Julian Noble
3 months ago
848 changed files with 461881 additions and 36131 deletions
@ -1,4 +1,4 @@ |
|||||||
Copyright (c) 2003-2012, Ashok P. Nadkarni |
Copyright (c) 2003-2024, Ashok P. Nadkarni |
||||||
All rights reserved. |
All rights reserved. |
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without |
Redistribution and use in source and binary forms, with or without |
@ -0,0 +1,73 @@ |
|||||||
|
# Tcl Windows API (TWAPI) extension |
||||||
|
|
||||||
|
The Tcl Windows API (TWAPI) extension provides access to the Windows API from |
||||||
|
within the Tcl scripting language. |
||||||
|
|
||||||
|
* Project source repository is at https://github.com/apnadkarni/twapi |
||||||
|
* Documentation is at https://twapi.magicsplat.com |
||||||
|
* Binary distribution is at https://sourceforge.net/projects/twapi/files/Current%20Releases/Tcl%20Windows%20API/ |
||||||
|
|
||||||
|
## Supported platforms |
||||||
|
|
||||||
|
TWAPI 5.0 requires |
||||||
|
|
||||||
|
* Windows 7 SP1 or later |
||||||
|
* Tcl 8.6.10+ or Tcl 9.x |
||||||
|
|
||||||
|
### Binary distribution |
||||||
|
|
||||||
|
The single binary distribution supports Tcl 8.6 and Tcl 9 for both 32- and |
||||||
|
64-bit platforms. |
||||||
|
|
||||||
|
It requires the VC++ runtime to already be installed |
||||||
|
on the system. Download from https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist if necessary. |
||||||
|
|
||||||
|
Windows 7 and 8.x also require the Windows UCRT runtime to be installed if not |
||||||
|
present. Download from https://support.microsoft.com/en-gb/topic/update-for-universal-c-runtime-in-windows-c0514201-7fe6-95a3-b0a5-287930f3560c. |
||||||
|
|
||||||
|
In most cases, both the above should already be present on the system. |
||||||
|
|
||||||
|
Note that the *modular* and single file *bin* in 4.x distributions are no longer |
||||||
|
available and will not be supported in 5.0. |
||||||
|
|
||||||
|
## TWAPI Summary |
||||||
|
|
||||||
|
The Tcl Windows API (TWAPI) extension provides access to the Windows API from |
||||||
|
within the Tcl scripting language. |
||||||
|
|
||||||
|
Functions in the following areas are implemented: |
||||||
|
|
||||||
|
* System functions including OS and CPU information, |
||||||
|
shutdown and message formatting |
||||||
|
* User and group management |
||||||
|
* COM client and server support |
||||||
|
* Security and resource access control |
||||||
|
* Window management |
||||||
|
* User input: generate key/mouse input and hotkeys |
||||||
|
* Basic sound playback functions |
||||||
|
* Windows services |
||||||
|
* Windows event log access |
||||||
|
* Windows event tracing |
||||||
|
* Process and thread management |
||||||
|
* Directory change monitoring |
||||||
|
* Lan Manager and file and print shares |
||||||
|
* Drive information, file system types etc. |
||||||
|
* Network configuration and statistics |
||||||
|
* Network connection monitoring and control |
||||||
|
* Named pipes |
||||||
|
* Clipboard access |
||||||
|
* Taskbar icons and notifications |
||||||
|
* Console mode functions |
||||||
|
* Window stations and desktops |
||||||
|
* Internationalization |
||||||
|
* Task scheduling |
||||||
|
* Shell functions |
||||||
|
* Registry |
||||||
|
* Windows Management Instrumentation |
||||||
|
* Windows Installer |
||||||
|
* Synchronization |
||||||
|
* Power management |
||||||
|
* Device I/O and management |
||||||
|
* Crypto API and certificates |
||||||
|
* SSL/TLS |
||||||
|
* Windows Performance Counters |
@ -0,0 +1,100 @@ |
|||||||
|
if {$::tcl_platform(platform) ne "windows"} { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
package ifneeded twapi_base 5.0b1 \ |
||||||
|
[list apply [list {dir} { |
||||||
|
package require platform |
||||||
|
set packageVer [string map {. {}} 5.0b1] |
||||||
|
if {[package vsatisfies [package require Tcl] 9]} { |
||||||
|
set baseDllName "tcl9twapi50b1.dll" |
||||||
|
} else { |
||||||
|
set baseDllName "twapi50b1t.dll" |
||||||
|
} |
||||||
|
set package "twapi" |
||||||
|
set package_ns ::$package |
||||||
|
namespace eval $package_ns {} |
||||||
|
set package_init_name [string totitle $package] |
||||||
|
|
||||||
|
# Try to load from current directory and if that fails try from |
||||||
|
# platform-specific directories. Note on failure to load when the DLL |
||||||
|
# exists, we do not try to load from other locations as twapi modules |
||||||
|
# may have been partially set up. |
||||||
|
|
||||||
|
set dllFound false |
||||||
|
foreach platform [linsert [::platform::patterns [platform::identify]] 0 .] { |
||||||
|
if {$platform eq "tcl"} continue |
||||||
|
set path [file join $dir $platform $baseDllName] |
||||||
|
if {[file exists $path]} { |
||||||
|
uplevel #0 [list load $path $package_init_name] |
||||||
|
set dllFound true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {!$dllFound} { |
||||||
|
error "Could not locate TWAPI dll." |
||||||
|
} |
||||||
|
|
||||||
|
# Load was successful |
||||||
|
set ${package_ns}::dllPath [file normalize $path] |
||||||
|
set ${package_ns}::packageDir $dir |
||||||
|
source [file join $dir twapi.tcl] |
||||||
|
package provide twapi_base 5.0b1 |
||||||
|
}] $dir] |
||||||
|
|
||||||
|
set __twapimods { |
||||||
|
com |
||||||
|
msi |
||||||
|
power |
||||||
|
printer |
||||||
|
synch |
||||||
|
security |
||||||
|
account |
||||||
|
apputil |
||||||
|
clipboard |
||||||
|
console |
||||||
|
crypto |
||||||
|
device |
||||||
|
etw |
||||||
|
eventlog |
||||||
|
mstask |
||||||
|
multimedia |
||||||
|
namedpipe |
||||||
|
network |
||||||
|
nls |
||||||
|
os |
||||||
|
pdh |
||||||
|
process |
||||||
|
rds |
||||||
|
registry |
||||||
|
resource |
||||||
|
service |
||||||
|
share |
||||||
|
shell |
||||||
|
storage |
||||||
|
ui |
||||||
|
input |
||||||
|
winsta |
||||||
|
wmi |
||||||
|
} |
||||||
|
foreach __twapimod $__twapimods { |
||||||
|
package ifneeded twapi_$__twapimod 5.0b1 \ |
||||||
|
[list apply [list {dir mod} { |
||||||
|
package require twapi_base 5.0b1 |
||||||
|
source [file join $dir $mod.tcl] |
||||||
|
package provide twapi_$mod 5.0b1 |
||||||
|
}] $dir $__twapimod] |
||||||
|
} |
||||||
|
|
||||||
|
package ifneeded twapi 5.0b1 \ |
||||||
|
[list apply [list {dir mods} { |
||||||
|
package require twapi_base 5.0b1 |
||||||
|
foreach mod $mods { |
||||||
|
package require twapi_$mod 5.0b1 |
||||||
|
} |
||||||
|
package provide twapi 5.0b1 |
||||||
|
}] $dir $__twapimods] |
||||||
|
|
||||||
|
unset __twapimod |
||||||
|
unset __twapimods |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,64 @@ |
|||||||
|
|
||||||
|
# Copyright (c) 2021 Ashok P. Nadkarni |
||||||
|
# All rights reserved. |
||||||
|
# |
||||||
|
# See the file LICENSE for license |
||||||
|
|
||||||
|
namespace eval twapi { |
||||||
|
variable _wts_session_monitors |
||||||
|
set _wts_session_monitors [dict create] |
||||||
|
} |
||||||
|
|
||||||
|
proc twapi::start_wts_session_monitor {script args} { |
||||||
|
variable _wts_session_monitors |
||||||
|
|
||||||
|
parseargs args { |
||||||
|
all |
||||||
|
} -maxleftover 0 -setvars] |
||||||
|
|
||||||
|
set script [lrange $script 0 end]; # Verify syntactically a list |
||||||
|
|
||||||
|
set id "wts#[TwapiId]" |
||||||
|
if {[dict size $_wts_session_monitors] == 0} { |
||||||
|
# No monitoring in progress. Start it |
||||||
|
# 0x2B1 -> WM_WTSSESSION_CHANGE |
||||||
|
Twapi_WTSRegisterSessionNotification $all |
||||||
|
_register_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_change_handler] 0 |
||||||
|
} |
||||||
|
|
||||||
|
dict set _wts_session_monitors $id $script |
||||||
|
return $id |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc twapi::stop_wts_session_monitor {id} { |
||||||
|
variable _wts_session_monitors |
||||||
|
|
||||||
|
if {![dict exists $_wts_session_monitors $id]} { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
dict unset _wts_session_monitors $id |
||||||
|
if {[dict size $_wts_session_monitors] == 0} { |
||||||
|
# 0x2B1 -> WM_WTSSESSION_CHANGE |
||||||
|
_unregister_script_wm_handler 0x2B1 [list [namespace current]::_wts_session_handler] |
||||||
|
Twapi_WTSUnRegisterSessionNotification |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc twapi::_wts_session_change_handler {msg change session_id msgpos ticks} { |
||||||
|
variable _wts_session_monitors |
||||||
|
|
||||||
|
if {[dict size $_wts_session_monitors] == 0} { |
||||||
|
return; # Not an error, could have deleted while already queued |
||||||
|
} |
||||||
|
|
||||||
|
dict for {id script} $_wts_session_monitors { |
||||||
|
set code [catch {uplevel #0 [linsert $script end $change $session_id]} msg] |
||||||
|
if {$code == 1} { |
||||||
|
# Error - put in background but we do not abort |
||||||
|
after 0 [list error $msg $::errorInfo $::errorCode] |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
@ -1,605 +0,0 @@ |
|||||||
# MeTOO stands for "MeTOO Emulates TclOO" (at a superficial syntactic level) |
|
||||||
# |
|
||||||
# Implements a *tiny*, but useful, subset of TclOO, primarily for use |
|
||||||
# with Tcl 8.4. Intent is that if you write code using MeToo, it should work |
|
||||||
# unmodified with TclOO in 8.5/8.6. Obviously, don't try going the other way! |
|
||||||
# |
|
||||||
# Emulation is superficial, don't try to be too clever in usage. |
|
||||||
# Doing funky, or even non-funky, things with object namespaces will |
|
||||||
# not work as you would expect. |
|
||||||
# |
|
||||||
# See the metoo::demo proc for sample usage. Calling this proc |
|
||||||
# with parameter "oo" will use the TclOO commands. Else the metoo:: |
|
||||||
# commands. Note the demo code remains the same for both. |
|
||||||
# |
|
||||||
# The following fragment uses MeToo only if TclOO is not available: |
|
||||||
# if {[llength [info commands oo::*]]} { |
|
||||||
# namespace import oo::* |
|
||||||
# } else { |
|
||||||
# source metoo.tcl |
|
||||||
# namespace import metoo::class |
|
||||||
# } |
|
||||||
# class create C {...} |
|
||||||
# |
|
||||||
# Summary of the TclOO subset implemented - see TclOO docs for detail : |
|
||||||
# |
|
||||||
# Creating a new class: |
|
||||||
# metoo::class create CLASSNAME CLASSDEFINITION |
|
||||||
# |
|
||||||
# Destroying a class: |
|
||||||
# CLASSNAME destroy |
|
||||||
# - this also destroys objects of that class and recursively destroys |
|
||||||
# child classes. NOTE: deleting the class namespace or renaming |
|
||||||
# the CLASSNAME command to "" will NOT call object destructors. |
|
||||||
# |
|
||||||
# CLASSDEFINITION: Following may appear in CLASSDEFINTION |
|
||||||
# method METHODNAME params METHODBODY |
|
||||||
# - same as TclOO |
|
||||||
# constructor params METHODBODY |
|
||||||
# - same syntax as TclOO |
|
||||||
# destructor METHODBODY |
|
||||||
# - same syntax as TclOO |
|
||||||
# unknown METHODNAME ARGS |
|
||||||
# - if defined, called when an undefined method is invoked |
|
||||||
# superclass SUPER |
|
||||||
# - inherits from SUPER. Unlike TclOO, only single inheritance. Also |
|
||||||
# no checks for inheritance loops. You'll find out quickly enough! |
|
||||||
# All other commands within a CLASSDEFINITION will either raise error or |
|
||||||
# work differently from TclOO. Actually you can use pretty much any |
|
||||||
# Tcl command inside CLASSDEFINITION but the results may not be what you |
|
||||||
# expect. Best to avoid this. |
|
||||||
# |
|
||||||
# METHODBODY: The following method-internal TclOO commands are available: |
|
||||||
# my METHODNAME ARGS |
|
||||||
# - to call another method METHODNAME |
|
||||||
# my variable VAR1 ?VAR2...? |
|
||||||
# - brings object-specific variables into scope |
|
||||||
# next ?ARGS? |
|
||||||
# - calls the superclass method of the same name |
|
||||||
# self |
|
||||||
# self object |
|
||||||
# - returns the object name (usable as a command) |
|
||||||
# self class |
|
||||||
# - returns class of this object |
|
||||||
# self namespace |
|
||||||
# - returns namespace of this object |
|
||||||
# |
|
||||||
# Creating objects: |
|
||||||
# CLASSNAME create OBJNAME ?ARGS? |
|
||||||
# - creates object OBJNAME of class CLASSNAME, passing ARGS to constructor |
|
||||||
# Returns the fully qualified object name that can be used as a command. |
|
||||||
# CLASSNAME new ?ARGS? |
|
||||||
# - creates a new object with an auto-generated name |
|
||||||
# |
|
||||||
# Destroying objects |
|
||||||
# OBJNAME destroy |
|
||||||
# - destroys the object calling destructors |
|
||||||
# rename OBJNAME "" |
|
||||||
# - same as above |
|
||||||
# |
|
||||||
# Renaming an object |
|
||||||
# rename OBJNAME NEWNAME |
|
||||||
# - the object can now be invoked using the new name. Note this is unlike |
|
||||||
# classes which should not be renamed. |
|
||||||
# |
|
||||||
# |
|
||||||
# Introspection (though different from TclOO) |
|
||||||
# metoo::introspect object isa OBJECT ?CLASSNAME? |
|
||||||
# - returns 1 if OBJECT is a metoo object and is of the specified class |
|
||||||
# if CLASSNAME is specified. Returns 0 otherwise. |
|
||||||
# metoo::introspect object list |
|
||||||
# - returns list of all objects |
|
||||||
# metoo::introspect class ancestors CLASSNAME |
|
||||||
# - returns list of ancestors for a class |
|
||||||
# |
|
||||||
# Differences and missing features from TclOO: Everything not listed above |
|
||||||
# is missing. Some notable differences: |
|
||||||
# - MeTOO is class-based, not object based like TclOO, thus class instances |
|
||||||
# (objects) cannot be modified by adding instance-specific methods etc.. |
|
||||||
# Also a class is not itself an object. |
|
||||||
# - Renaming classes does not work and will fail in mysterious ways |
|
||||||
# - does not support class refinement/definition |
|
||||||
# - no variable command at class level for automatically bringing variables |
|
||||||
# into scope |
|
||||||
# - no filters, forwarding, multiple-inheritance |
|
||||||
# - no private methods (all methods are exported). |
|
||||||
|
|
||||||
# NOTE: file must be sourced at global level since metoo namespace is expected |
|
||||||
# to be top level namespace |
|
||||||
|
|
||||||
# DO NOT DO THIS. ELSE TESTS FAIL BECAUSE they define tests in the |
|
||||||
# metoo namespace which then get deleted by the line below when |
|
||||||
# the package is lazy auto-loaded |
|
||||||
# catch {namespace delete metoo} |
|
||||||
|
|
||||||
# TBD - variable ("my variable" is done, "variable" in method or |
|
||||||
# class definition is not) |
|
||||||
# TBD - default constructor and destructor to "next" (or maybe that |
|
||||||
# is already taken care of by the inheritance code |
|
||||||
|
|
||||||
namespace eval metoo { |
|
||||||
variable next_id 0 |
|
||||||
|
|
||||||
variable _objects; # Maps objects to its namespace |
|
||||||
array set _objects {} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
# Namespace in which commands in a class definition block are called |
|
||||||
namespace eval metoo::define { |
|
||||||
proc method {class_ns name params body} { |
|
||||||
# Methods are defined in the methods subspace of the class namespace. |
|
||||||
# We prefix with _m_ to prevent them from being directly called |
|
||||||
# as procs, for example if the method is a Tcl command like "set" |
|
||||||
# The first parameter to a method is always the object namespace |
|
||||||
# denoted as the paramter "_this" |
|
||||||
namespace eval ${class_ns}::methods [list proc _m_$name [concat [list _this] $params] $body] |
|
||||||
|
|
||||||
} |
|
||||||
proc superclass {class_ns superclass} { |
|
||||||
if {[info exists ${class_ns}::super]} { |
|
||||||
error "Only one superclass allowed for a class" |
|
||||||
} |
|
||||||
set sup [uplevel 3 "namespace eval $superclass {namespace current}"] |
|
||||||
set ${class_ns}::super $sup |
|
||||||
# We store the subclass in the super so it can be destroyed |
|
||||||
# if the super is destroyed. |
|
||||||
set ${sup}::subclasses($class_ns) 1 |
|
||||||
} |
|
||||||
proc constructor {class_ns params body} { |
|
||||||
method $class_ns constructor $params $body |
|
||||||
} |
|
||||||
proc destructor {class_ns body} { |
|
||||||
method $class_ns destructor {} $body |
|
||||||
} |
|
||||||
proc export {args} { |
|
||||||
# Nothing to do, all methods are exported anyways |
|
||||||
# Command is here for compatibility only |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Namespace in which commands used in objects methods are defined |
|
||||||
# (self, my etc.) |
|
||||||
namespace eval metoo::object { |
|
||||||
proc next {args} { |
|
||||||
upvar 1 _this this; # object namespace |
|
||||||
|
|
||||||
# Figure out what class context this is executing in. Note |
|
||||||
# we cannot use _this in caller since that is the object namespace |
|
||||||
# which is not necessarily related to the current class namespace. |
|
||||||
set class_ns [namespace parent [uplevel 1 {namespace current}]] |
|
||||||
|
|
||||||
# Figure out the current method being called |
|
||||||
set methodname [namespace tail [lindex [uplevel 1 {info level 0}] 0]] |
|
||||||
|
|
||||||
# Find the next method in the class hierarchy and call it |
|
||||||
while {[info exists ${class_ns}::super]} { |
|
||||||
set class_ns [set ${class_ns}::super] |
|
||||||
if {[llength [info commands ${class_ns}::methods::$methodname]]} { |
|
||||||
return [uplevel 1 [list ${class_ns}::methods::$methodname $this] $args] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
error "'next' command has no receiver in the hierarchy for method $methodname" |
|
||||||
} |
|
||||||
|
|
||||||
proc self {{what object}} { |
|
||||||
upvar 1 _this this |
|
||||||
switch -exact -- $what { |
|
||||||
class { return [namespace parent $this] } |
|
||||||
namespace { return $this } |
|
||||||
object { return [set ${this}::_(name)] } |
|
||||||
default { |
|
||||||
error "Argument '$what' not understood by self method" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
proc my {methodname args} { |
|
||||||
# We insert the object namespace as the first parameter to the command. |
|
||||||
# This is passed as the first parameter "_this" to methods. Since |
|
||||||
# "my" can be only called from methods, we can retrieve it fro |
|
||||||
# our caller. |
|
||||||
upvar 1 _this this; # object namespace |
|
||||||
|
|
||||||
set class_ns [namespace parent $this] |
|
||||||
|
|
||||||
set meth [::metoo::_locate_method $class_ns $methodname] |
|
||||||
if {$meth ne ""} { |
|
||||||
# We need to invoke in the caller's context so upvar etc. will |
|
||||||
# not be affected by this intermediate method dispatcher |
|
||||||
return [uplevel 1 [list $meth $this] $args] |
|
||||||
} |
|
||||||
|
|
||||||
# It is ok for constructor or destructor to be undefined. For |
|
||||||
# the others, invoke "unknown" if it exists |
|
||||||
if {$methodname eq "constructor" || $methodname eq "destructor"} { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
set meth [::metoo::_locate_method $class_ns "unknown"] |
|
||||||
if {$meth ne ""} { |
|
||||||
# We need to invoke in the caller's context so upvar etc. will |
|
||||||
# not be affected by this intermediate method dispatcher |
|
||||||
return [uplevel 1 [list $meth $this $methodname] $args] |
|
||||||
} |
|
||||||
|
|
||||||
error "Unknown method $methodname" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Given a method name, locate it in the class hierarchy. Returns |
|
||||||
# fully qualified method if found, else an empty string |
|
||||||
proc metoo::_locate_method {class_ns methodname} { |
|
||||||
# See if there is a method defined in this class. |
|
||||||
# Breakage if method names with wildcard chars. Too bad |
|
||||||
if {[llength [info commands ${class_ns}::methods::_m_$methodname]]} { |
|
||||||
# We need to invoke in the caller's context so upvar etc. will |
|
||||||
# not be affected by this intermediate method dispatcher |
|
||||||
return ${class_ns}::methods::_m_$methodname |
|
||||||
} |
|
||||||
|
|
||||||
# No method here, check for super class. |
|
||||||
while {[info exists ${class_ns}::super]} { |
|
||||||
set class_ns [set ${class_ns}::super] |
|
||||||
if {[llength [info commands ${class_ns}::methods::_m_$methodname]]} { |
|
||||||
return ${class_ns}::methods::_m_$methodname |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return ""; # Not found |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::_new {class_ns cmd args} { |
|
||||||
# class_ns expected to be fully qualified |
|
||||||
variable next_id |
|
||||||
|
|
||||||
# IMPORTANT: |
|
||||||
# object namespace *must* be child of class namespace. |
|
||||||
# Saves a bit of bookkeeping. Putting it somewhere else will require |
|
||||||
# changes to many other places in the code. |
|
||||||
set objns ${class_ns}::o#[incr next_id] |
|
||||||
|
|
||||||
switch -exact -- $cmd { |
|
||||||
create { |
|
||||||
if {[llength $args] < 1} { |
|
||||||
error "Insufficient args, should be: class create CLASSNAME ?args?" |
|
||||||
} |
|
||||||
# TBD - check if command already exists |
|
||||||
# Note objname must always be fully qualified. Note cannot |
|
||||||
# use namespace which here because the commmand does not |
|
||||||
# yet exist. |
|
||||||
set args [lassign $args objname] |
|
||||||
if {[string compare :: [string range $objname 0 1]]} { |
|
||||||
# Not fully qualified. Qualify based on caller namespace |
|
||||||
set objname [uplevel 1 "namespace current"]::$objname |
|
||||||
} |
|
||||||
# Trip excess ":" - can happen in both above cases |
|
||||||
set objname ::[string trimleft $objname :] |
|
||||||
} |
|
||||||
new { |
|
||||||
set objname $objns |
|
||||||
} |
|
||||||
default { |
|
||||||
error "Unknown command '$cmd'. Should be create or new." |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Create the namespace. The array _ is used to hold private information |
|
||||||
namespace eval $objns { |
|
||||||
variable _ |
|
||||||
} |
|
||||||
set ${objns}::_(name) $objname |
|
||||||
|
|
||||||
# When invoked by its name, call the dispatcher. |
|
||||||
interp alias {} $objname {} ${class_ns}::_call $objns |
|
||||||
|
|
||||||
# Register the object. We do this BEFORE running the constructor |
|
||||||
variable _objects |
|
||||||
set _objects($objname) $objns |
|
||||||
|
|
||||||
# Invoke the constructor |
|
||||||
if {[catch { |
|
||||||
$objname constructor {*}$args |
|
||||||
} msg]} { |
|
||||||
# Undo what we did |
|
||||||
set erinfo $::errorInfo |
|
||||||
set ercode $::errorCode |
|
||||||
rename $objname "" |
|
||||||
namespace delete $objns |
|
||||||
error $msg $erinfo $ercode |
|
||||||
} |
|
||||||
|
|
||||||
# TBD - does tracing cause a slowdown ? |
|
||||||
# Set up trace to track when the object is renamed/destroyed |
|
||||||
trace add command $objname {rename delete} [list [namespace current]::_trace_object_renames $objns] |
|
||||||
|
|
||||||
return $objname |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::_trace_object_renames {objns oldname newname op} { |
|
||||||
# Note the trace command fully qualifies oldname and newname |
|
||||||
if {$op eq "rename"} { |
|
||||||
variable _objects |
|
||||||
set _objects($newname) $_objects($oldname) |
|
||||||
unset _objects($oldname) |
|
||||||
set ${objns}::_(name) $newname |
|
||||||
} else { |
|
||||||
$oldname destroy |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::_class_cmd {class_ns cmd args} { |
|
||||||
switch -exact -- $cmd { |
|
||||||
create - |
|
||||||
new { |
|
||||||
return [uplevel 1 [list [namespace current]::_new $class_ns $cmd] $args] |
|
||||||
} |
|
||||||
destroy { |
|
||||||
# Destroy all objects belonging to this class |
|
||||||
foreach objns [namespace children ${class_ns} o#*] { |
|
||||||
[set ${objns}::_(name)] destroy |
|
||||||
} |
|
||||||
# Destroy all classes that inherit from this |
|
||||||
foreach child_ns [array names ${class_ns}::subclasses] { |
|
||||||
# Child namespace is also subclass command |
|
||||||
$child_ns destroy |
|
||||||
} |
|
||||||
trace remove command $class_ns {rename delete} [list ::metoo::_trace_class_renames] |
|
||||||
namespace delete ${class_ns} |
|
||||||
rename ${class_ns} "" |
|
||||||
} |
|
||||||
default { |
|
||||||
error "Unknown command '$cmd'. Should be create, new or destroy." |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::class {cmd cname definition} { |
|
||||||
variable next_id |
|
||||||
|
|
||||||
if {$cmd ne "create"} { |
|
||||||
error "Syntax: class create CLASSNAME DEFINITION" |
|
||||||
} |
|
||||||
|
|
||||||
if {[uplevel 1 "namespace exists $cname"]} { |
|
||||||
error "can't create class '$cname': namespace already exists with that name." |
|
||||||
} |
|
||||||
|
|
||||||
# Resolve cname into a namespace in the caller's context |
|
||||||
set class_ns [uplevel 1 "namespace eval $cname {namespace current}"] |
|
||||||
|
|
||||||
if {[llength [info commands $class_ns]]} { |
|
||||||
# Delete the namespace we just created |
|
||||||
namespace delete $class_ns |
|
||||||
error "can't create class '$cname': command already exists with that name." |
|
||||||
} |
|
||||||
|
|
||||||
# Define the commands/aliases that are used inside a class definition |
|
||||||
foreach procname [info commands [namespace current]::define::*] { |
|
||||||
interp alias {} ${class_ns}::[namespace tail $procname] {} $procname $class_ns |
|
||||||
} |
|
||||||
|
|
||||||
# Define the built in commands callable within class instance methods |
|
||||||
foreach procname [info commands [namespace current]::object::*] { |
|
||||||
interp alias {} ${class_ns}::methods::[namespace tail $procname] {} $procname |
|
||||||
} |
|
||||||
|
|
||||||
# Define the destroy method for the class object instances |
|
||||||
namespace eval $class_ns { |
|
||||||
method destroy {} { |
|
||||||
set retval [my destructor] |
|
||||||
# Remove trace on command rename/deletion. |
|
||||||
# ${_this}::_(name) contains the object's current name on |
|
||||||
# which the trace is set. |
|
||||||
set me [set ${_this}::_(name)] |
|
||||||
trace remove command $me {rename delete} [list ::metoo::_trace_object_renames $_this] |
|
||||||
rename $me "" |
|
||||||
unset -nocomplain ::metoo::_objects($me) |
|
||||||
namespace delete $_this |
|
||||||
return $retval |
|
||||||
} |
|
||||||
method variable {args} { |
|
||||||
if {[llength $args]} { |
|
||||||
set cmd [list upvar 0] |
|
||||||
foreach varname $args { |
|
||||||
lappend cmd ${_this}::$varname $varname |
|
||||||
} |
|
||||||
uplevel 1 $cmd |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# Define the class. Note we do this *after* the standard |
|
||||||
# definitions (destroy etc.) above so that they can |
|
||||||
# be overridden by the class definition. |
|
||||||
if {[catch { |
|
||||||
namespace eval $class_ns $definition |
|
||||||
} msg ]} { |
|
||||||
namespace delete $class_ns |
|
||||||
error $msg $::errorInfo $::errorCode |
|
||||||
} |
|
||||||
|
|
||||||
# Also define the call dispatcher within the class. |
|
||||||
# TBD - not sure this is actually necessary any more |
|
||||||
namespace eval ${class_ns} { |
|
||||||
proc _call {objns methodname args} { |
|
||||||
# Note this duplicates the "my" code but cannot call that as |
|
||||||
# it adds another frame level which interferes with uplevel etc. |
|
||||||
|
|
||||||
set class_ns [namespace parent $objns] |
|
||||||
|
|
||||||
# We insert the object namespace as the first param to the command. |
|
||||||
# This is passed as the first parameter "_this" to methods. |
|
||||||
|
|
||||||
set meth [::metoo::_locate_method $class_ns $methodname] |
|
||||||
if {$meth ne ""} { |
|
||||||
# We need to invoke in the caller's context so upvar etc. will |
|
||||||
# not be affected by this intermediate method dispatcher |
|
||||||
return [uplevel 1 [list $meth $objns] $args] |
|
||||||
} |
|
||||||
|
|
||||||
# It is ok for constructor or destructor to be undefined. For |
|
||||||
# the others, invoke "unknown" if it exists |
|
||||||
|
|
||||||
if {$methodname eq "constructor" || $methodname eq "destructor"} { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
set meth [::metoo::_locate_method $class_ns "unknown"] |
|
||||||
if {$meth ne ""} { |
|
||||||
# We need to invoke in the caller's context so upvar etc. will |
|
||||||
# not be affected by this intermediate method dispatcher |
|
||||||
return [uplevel 1 [list $meth $objns $methodname] $args] |
|
||||||
} |
|
||||||
|
|
||||||
error "Unknown method $methodname" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
# The namespace is also a command used to create class instances |
|
||||||
# TBD - check if command of that name already exists |
|
||||||
interp alias {} $class_ns {} [namespace current]::_class_cmd $class_ns |
|
||||||
# Set up trace to track when the class command is renamed/destroyed |
|
||||||
trace add command $class_ns [list rename delete] ::metoo::_trace_class_renames |
|
||||||
|
|
||||||
return $class_ns |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::_trace_class_renames {oldname newname op} { |
|
||||||
if {$op eq "rename"} { |
|
||||||
# TBD - this does not actually work. The rename succeeds anyways |
|
||||||
error "MetOO classes may not be renamed" |
|
||||||
} else { |
|
||||||
$oldname destroy |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::introspect {type info args} { |
|
||||||
switch -exact -- $type { |
|
||||||
"object" { |
|
||||||
variable _objects |
|
||||||
switch -exact -- $info { |
|
||||||
"isa" { |
|
||||||
if {[llength $args] == 0 || [llength $args] > 2} { |
|
||||||
error "wrong # args: should be \"metoo::introspect $type $info OBJNAME ?CLASS?\"" |
|
||||||
} |
|
||||||
set objname [uplevel 1 [list namespace which -command [lindex $args 0]]] |
|
||||||
if {![info exists _objects($objname)]} { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
if {[llength $args] == 1} { |
|
||||||
# No class specified |
|
||||||
return 1 |
|
||||||
} |
|
||||||
# passed classname assumed to be fully qualified |
|
||||||
set objclass [namespace parent $_objects($objname)] |
|
||||||
if {[string equal $objclass [lindex $args 1]]} { |
|
||||||
# Direct hit |
|
||||||
return 1 |
|
||||||
} |
|
||||||
|
|
||||||
# No direct hit, check ancestors |
|
||||||
if {[lindex $args 1] in [ancestors $objclass]} { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
|
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
"list" { |
|
||||||
if {[llength $args] > 1} { |
|
||||||
error "wrong # args: should be \"metoo::introspect $type $info ?CLASS?" |
|
||||||
} |
|
||||||
variable _objects |
|
||||||
if {[llength $args] == 0} { |
|
||||||
return [array names _objects] |
|
||||||
} |
|
||||||
set objs {} |
|
||||||
foreach obj [array names _objects] { |
|
||||||
if {[introspect object isa $obj [lindex $args 0]]} { |
|
||||||
lappend objs $obj |
|
||||||
} |
|
||||||
} |
|
||||||
return $objs |
|
||||||
} |
|
||||||
default { |
|
||||||
error "$info subcommand not supported for $type introspection" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
"class" { |
|
||||||
switch -exact -- $info { |
|
||||||
"ancestors" { |
|
||||||
if {[llength $args] != 1} { |
|
||||||
error "wrong # args: should be \"metoo::introspect $type $info CLASSNAME" |
|
||||||
} |
|
||||||
return [ancestors [lindex $args 0]] |
|
||||||
} |
|
||||||
default { |
|
||||||
error "$info subcommand not supported for $type introspection" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
default { |
|
||||||
error "$type introspection not supported" |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
proc metoo::ancestors {class_ns} { |
|
||||||
# Returns ancestors of a class |
|
||||||
|
|
||||||
set ancestors [list ] |
|
||||||
while {[info exists ${class_ns}::super]} { |
|
||||||
lappend ancestors [set class_ns [set ${class_ns}::super]] |
|
||||||
} |
|
||||||
|
|
||||||
return $ancestors |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval metoo { namespace export class } |
|
||||||
|
|
||||||
# Simple sample class showing all capabilities. Anything not shown here will |
|
||||||
# probably not work. Call as "demo" to use metoo, or "demo oo" to use TclOO. |
|
||||||
# Output should be same in both cases. |
|
||||||
proc ::metoo::demo {{ns metoo}} { |
|
||||||
${ns}::class create Base { |
|
||||||
constructor {x y} { puts "Base constructor ([self object]): $x, $y" |
|
||||||
} |
|
||||||
method m {} { puts "Base::m called" } |
|
||||||
method n {args} { puts "Base::n called: [join $args {, }]"; my m } |
|
||||||
method unknown {methodname args} { puts "Base::unknown called for $methodname [join $args {, }]"} |
|
||||||
destructor { puts "Base::destructor ([self object])" } |
|
||||||
} |
|
||||||
|
|
||||||
${ns}::class create Derived { |
|
||||||
superclass Base |
|
||||||
constructor {x y} { puts "Derived constructor ([self object]): $x, $y" ; next $x $y } |
|
||||||
destructor { puts "Derived::destructor called ([self object])" ; next } |
|
||||||
method n {args} { puts "Derived::n ([self object]): [join $args {, }]"; next {*}$args} |
|
||||||
method put {val} {my variable var ; set var $val} |
|
||||||
method get {varname} {my variable var ; upvar 1 $varname retvar; set retvar $var} |
|
||||||
} |
|
||||||
|
|
||||||
Base create b dum dee; # Create named object |
|
||||||
Derived create d fee fi; # Create derived object |
|
||||||
set o [Derived new fo fum]; # Create autonamed object |
|
||||||
$o put 10; # Use of instance variable |
|
||||||
$o get v; # Verify correct frame level ... |
|
||||||
puts "v:$v"; # ...when calling methods |
|
||||||
b m; # Direct method |
|
||||||
b n; # Use of my to call another method |
|
||||||
$o m; # Inherited method |
|
||||||
$o n; # Overridden method chained to inherited |
|
||||||
$o nosuchmethod arg1 arg2; # Invoke unknown |
|
||||||
$o destroy; # Explicit destroy |
|
||||||
rename b ""; # Destroy through rename |
|
||||||
Base destroy; # Should destroy object d, Derived, Base |
|
||||||
} |
|
||||||
|
|
||||||
# Hack to work with the various build configuration. |
|
||||||
if {[info commands ::twapi::get_version] ne ""} { |
|
||||||
package provide metoo [::twapi::get_version -patchlevel] |
|
||||||
} |
|
@ -1,119 +0,0 @@ |
|||||||
# |
|
||||||
# Tcl package index file |
|
||||||
# |
|
||||||
|
|
||||||
namespace eval twapi { |
|
||||||
variable scriptdir |
|
||||||
proc set_scriptdir dir {variable scriptdir ; set scriptdir $dir} |
|
||||||
} |
|
||||||
|
|
||||||
package ifneeded twapi_base 4.7.2 \ |
|
||||||
[list load [file join $dir twapi472.dll] twapi_base] |
|
||||||
package ifneeded twapi_com 4.7.2 \ |
|
||||||
{load {} twapi_com} |
|
||||||
package ifneeded metoo 4.7.2 \ |
|
||||||
[list source [file join $dir metoo.tcl]] |
|
||||||
package ifneeded twapi_com 4.7.2 \ |
|
||||||
{load {} twapi_com} |
|
||||||
package ifneeded twapi_msi 4.7.2 \ |
|
||||||
[list source [file join $dir msi.tcl]] |
|
||||||
package ifneeded twapi_power 4.7.2 \ |
|
||||||
[list source [file join $dir power.tcl]] |
|
||||||
package ifneeded twapi_printer 4.7.2 \ |
|
||||||
[list source [file join $dir printer.tcl]] |
|
||||||
package ifneeded twapi_synch 4.7.2 \ |
|
||||||
[list source [file join $dir synch.tcl]] |
|
||||||
package ifneeded twapi_security 4.7.2 \ |
|
||||||
{load {} twapi_security} |
|
||||||
package ifneeded twapi_account 4.7.2 \ |
|
||||||
{load {} twapi_account} |
|
||||||
package ifneeded twapi_apputil 4.7.2 \ |
|
||||||
{load {} twapi_apputil} |
|
||||||
package ifneeded twapi_clipboard 4.7.2 \ |
|
||||||
{load {} twapi_clipboard} |
|
||||||
package ifneeded twapi_console 4.7.2 \ |
|
||||||
{load {} twapi_console} |
|
||||||
package ifneeded twapi_crypto 4.7.2 \ |
|
||||||
{load {} twapi_crypto} |
|
||||||
package ifneeded twapi_device 4.7.2 \ |
|
||||||
{load {} twapi_device} |
|
||||||
package ifneeded twapi_etw 4.7.2 \ |
|
||||||
{load {} twapi_etw} |
|
||||||
package ifneeded twapi_eventlog 4.7.2 \ |
|
||||||
{load {} twapi_eventlog} |
|
||||||
package ifneeded twapi_mstask 4.7.2 \ |
|
||||||
{load {} twapi_mstask} |
|
||||||
package ifneeded twapi_multimedia 4.7.2 \ |
|
||||||
{load {} twapi_multimedia} |
|
||||||
package ifneeded twapi_namedpipe 4.7.2 \ |
|
||||||
{load {} twapi_namedpipe} |
|
||||||
package ifneeded twapi_network 4.7.2 \ |
|
||||||
{load {} twapi_network} |
|
||||||
package ifneeded twapi_nls 4.7.2 \ |
|
||||||
{load {} twapi_nls} |
|
||||||
package ifneeded twapi_os 4.7.2 \ |
|
||||||
{load {} twapi_os} |
|
||||||
package ifneeded twapi_pdh 4.7.2 \ |
|
||||||
{load {} twapi_pdh} |
|
||||||
package ifneeded twapi_process 4.7.2 \ |
|
||||||
{load {} twapi_process} |
|
||||||
package ifneeded twapi_rds 4.7.2 \ |
|
||||||
{load {} twapi_rds} |
|
||||||
package ifneeded twapi_resource 4.7.2 \ |
|
||||||
{load {} twapi_resource} |
|
||||||
package ifneeded twapi_service 4.7.2 \ |
|
||||||
{load {} twapi_service} |
|
||||||
package ifneeded twapi_share 4.7.2 \ |
|
||||||
{load {} twapi_share} |
|
||||||
package ifneeded twapi_shell 4.7.2 \ |
|
||||||
{load {} twapi_shell} |
|
||||||
package ifneeded twapi_storage 4.7.2 \ |
|
||||||
{load {} twapi_storage} |
|
||||||
package ifneeded twapi_ui 4.7.2 \ |
|
||||||
{load {} twapi_ui} |
|
||||||
package ifneeded twapi_input 4.7.2 \ |
|
||||||
{load {} twapi_input} |
|
||||||
package ifneeded twapi_winsta 4.7.2 \ |
|
||||||
{load {} twapi_winsta} |
|
||||||
package ifneeded twapi_wmi 4.7.2 \ |
|
||||||
{load {} twapi_wmi} |
|
||||||
|
|
||||||
package ifneeded twapi 4.7.2 [subst { |
|
||||||
twapi::set_scriptdir [list $dir] |
|
||||||
package require twapi_base 4.7.2 |
|
||||||
source [list [file join $dir twapi_entry.tcl]] |
|
||||||
package require metoo 4.7.2 |
|
||||||
package require twapi_com 4.7.2 |
|
||||||
package require twapi_msi 4.7.2 |
|
||||||
package require twapi_power 4.7.2 |
|
||||||
package require twapi_printer 4.7.2 |
|
||||||
package require twapi_synch 4.7.2 |
|
||||||
package require twapi_security 4.7.2 |
|
||||||
package require twapi_account 4.7.2 |
|
||||||
package require twapi_apputil 4.7.2 |
|
||||||
package require twapi_clipboard 4.7.2 |
|
||||||
package require twapi_console 4.7.2 |
|
||||||
package require twapi_crypto 4.7.2 |
|
||||||
package require twapi_device 4.7.2 |
|
||||||
package require twapi_etw 4.7.2 |
|
||||||
package require twapi_eventlog 4.7.2 |
|
||||||
package require twapi_mstask 4.7.2 |
|
||||||
package require twapi_multimedia 4.7.2 |
|
||||||
package require twapi_namedpipe 4.7.2 |
|
||||||
package require twapi_network 4.7.2 |
|
||||||
package require twapi_nls 4.7.2 |
|
||||||
package require twapi_os 4.7.2 |
|
||||||
package require twapi_pdh 4.7.2 |
|
||||||
package require twapi_process 4.7.2 |
|
||||||
package require twapi_rds 4.7.2 |
|
||||||
package require twapi_resource 4.7.2 |
|
||||||
package require twapi_service 4.7.2 |
|
||||||
package require twapi_share 4.7.2 |
|
||||||
package require twapi_shell 4.7.2 |
|
||||||
package require twapi_storage 4.7.2 |
|
||||||
package require twapi_ui 4.7.2 |
|
||||||
package require twapi_input 4.7.2 |
|
||||||
package require twapi_winsta 4.7.2 |
|
||||||
package require twapi_wmi 4.7.2 |
|
||||||
package provide twapi 4.7.2 |
|
||||||
}] |
|
Binary file not shown.
@ -1,11 +0,0 @@ |
|||||||
# -*- tcl -*- |
|
||||||
namespace eval twapi { |
|
||||||
variable version |
|
||||||
set version(twapi) 4.7.2 |
|
||||||
variable patchlevel 4.7.2 |
|
||||||
variable package_name twapi |
|
||||||
variable dll_base_name twapi[string map {. {}} 4.7.2] |
|
||||||
variable scriptdir [file dirname [info script]] |
|
||||||
} |
|
||||||
|
|
||||||
source [file join $twapi::scriptdir twapi.tcl] |
|
@ -0,0 +1,8 @@ |
|||||||
|
# Compatibility wrapper for deprecated packages. |
||||||
|
## |
||||||
|
# Stages |
||||||
|
# [D1] Next Release - Noted deprecated, with redirection wrappers |
||||||
|
# [D2] Release After - Wrappers become Blockers, throwing error noting redirection |
||||||
|
# [D3] Release Beyond - All removed. |
||||||
|
## |
||||||
|
# Currently in deprecation [[NONE]] |
@ -0,0 +1,625 @@ |
|||||||
|
# aes.tcl - |
||||||
|
# |
||||||
|
# Copyright (c) 2005 Thorsten Schloermann |
||||||
|
# Copyright (c) 2005 Pat Thoyts <patthoyts@users.sourceforge.net> |
||||||
|
# Copyright (c) 2013 Andreas Kupries |
||||||
|
# |
||||||
|
# A Tcl implementation of the Advanced Encryption Standard (US FIPS PUB 197) |
||||||
|
# |
||||||
|
# AES is a block cipher with a block size of 128 bits and a variable |
||||||
|
# key size of 128, 192 or 256 bits. |
||||||
|
# The algorithm works on each block as a 4x4 state array. There are 4 steps |
||||||
|
# in each round: |
||||||
|
# SubBytes a non-linear substitution step using a predefined S-box |
||||||
|
# ShiftRows cyclic transposition of rows in the state matrix |
||||||
|
# MixColumns transformation upon columns in the state matrix |
||||||
|
# AddRoundKey application of round specific sub-key |
||||||
|
# |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ::aes { |
||||||
|
variable uid |
||||||
|
if {![info exists uid]} { set uid 0 } |
||||||
|
|
||||||
|
namespace export aes |
||||||
|
|
||||||
|
# constants |
||||||
|
|
||||||
|
# S-box |
||||||
|
variable sbox { |
||||||
|
0x63 0x7c 0x77 0x7b 0xf2 0x6b 0x6f 0xc5 0x30 0x01 0x67 0x2b 0xfe 0xd7 0xab 0x76 |
||||||
|
0xca 0x82 0xc9 0x7d 0xfa 0x59 0x47 0xf0 0xad 0xd4 0xa2 0xaf 0x9c 0xa4 0x72 0xc0 |
||||||
|
0xb7 0xfd 0x93 0x26 0x36 0x3f 0xf7 0xcc 0x34 0xa5 0xe5 0xf1 0x71 0xd8 0x31 0x15 |
||||||
|
0x04 0xc7 0x23 0xc3 0x18 0x96 0x05 0x9a 0x07 0x12 0x80 0xe2 0xeb 0x27 0xb2 0x75 |
||||||
|
0x09 0x83 0x2c 0x1a 0x1b 0x6e 0x5a 0xa0 0x52 0x3b 0xd6 0xb3 0x29 0xe3 0x2f 0x84 |
||||||
|
0x53 0xd1 0x00 0xed 0x20 0xfc 0xb1 0x5b 0x6a 0xcb 0xbe 0x39 0x4a 0x4c 0x58 0xcf |
||||||
|
0xd0 0xef 0xaa 0xfb 0x43 0x4d 0x33 0x85 0x45 0xf9 0x02 0x7f 0x50 0x3c 0x9f 0xa8 |
||||||
|
0x51 0xa3 0x40 0x8f 0x92 0x9d 0x38 0xf5 0xbc 0xb6 0xda 0x21 0x10 0xff 0xf3 0xd2 |
||||||
|
0xcd 0x0c 0x13 0xec 0x5f 0x97 0x44 0x17 0xc4 0xa7 0x7e 0x3d 0x64 0x5d 0x19 0x73 |
||||||
|
0x60 0x81 0x4f 0xdc 0x22 0x2a 0x90 0x88 0x46 0xee 0xb8 0x14 0xde 0x5e 0x0b 0xdb |
||||||
|
0xe0 0x32 0x3a 0x0a 0x49 0x06 0x24 0x5c 0xc2 0xd3 0xac 0x62 0x91 0x95 0xe4 0x79 |
||||||
|
0xe7 0xc8 0x37 0x6d 0x8d 0xd5 0x4e 0xa9 0x6c 0x56 0xf4 0xea 0x65 0x7a 0xae 0x08 |
||||||
|
0xba 0x78 0x25 0x2e 0x1c 0xa6 0xb4 0xc6 0xe8 0xdd 0x74 0x1f 0x4b 0xbd 0x8b 0x8a |
||||||
|
0x70 0x3e 0xb5 0x66 0x48 0x03 0xf6 0x0e 0x61 0x35 0x57 0xb9 0x86 0xc1 0x1d 0x9e |
||||||
|
0xe1 0xf8 0x98 0x11 0x69 0xd9 0x8e 0x94 0x9b 0x1e 0x87 0xe9 0xce 0x55 0x28 0xdf |
||||||
|
0x8c 0xa1 0x89 0x0d 0xbf 0xe6 0x42 0x68 0x41 0x99 0x2d 0x0f 0xb0 0x54 0xbb 0x16 |
||||||
|
} |
||||||
|
# inverse S-box |
||||||
|
variable xobs { |
||||||
|
0x52 0x09 0x6a 0xd5 0x30 0x36 0xa5 0x38 0xbf 0x40 0xa3 0x9e 0x81 0xf3 0xd7 0xfb |
||||||
|
0x7c 0xe3 0x39 0x82 0x9b 0x2f 0xff 0x87 0x34 0x8e 0x43 0x44 0xc4 0xde 0xe9 0xcb |
||||||
|
0x54 0x7b 0x94 0x32 0xa6 0xc2 0x23 0x3d 0xee 0x4c 0x95 0x0b 0x42 0xfa 0xc3 0x4e |
||||||
|
0x08 0x2e 0xa1 0x66 0x28 0xd9 0x24 0xb2 0x76 0x5b 0xa2 0x49 0x6d 0x8b 0xd1 0x25 |
||||||
|
0x72 0xf8 0xf6 0x64 0x86 0x68 0x98 0x16 0xd4 0xa4 0x5c 0xcc 0x5d 0x65 0xb6 0x92 |
||||||
|
0x6c 0x70 0x48 0x50 0xfd 0xed 0xb9 0xda 0x5e 0x15 0x46 0x57 0xa7 0x8d 0x9d 0x84 |
||||||
|
0x90 0xd8 0xab 0x00 0x8c 0xbc 0xd3 0x0a 0xf7 0xe4 0x58 0x05 0xb8 0xb3 0x45 0x06 |
||||||
|
0xd0 0x2c 0x1e 0x8f 0xca 0x3f 0x0f 0x02 0xc1 0xaf 0xbd 0x03 0x01 0x13 0x8a 0x6b |
||||||
|
0x3a 0x91 0x11 0x41 0x4f 0x67 0xdc 0xea 0x97 0xf2 0xcf 0xce 0xf0 0xb4 0xe6 0x73 |
||||||
|
0x96 0xac 0x74 0x22 0xe7 0xad 0x35 0x85 0xe2 0xf9 0x37 0xe8 0x1c 0x75 0xdf 0x6e |
||||||
|
0x47 0xf1 0x1a 0x71 0x1d 0x29 0xc5 0x89 0x6f 0xb7 0x62 0x0e 0xaa 0x18 0xbe 0x1b |
||||||
|
0xfc 0x56 0x3e 0x4b 0xc6 0xd2 0x79 0x20 0x9a 0xdb 0xc0 0xfe 0x78 0xcd 0x5a 0xf4 |
||||||
|
0x1f 0xdd 0xa8 0x33 0x88 0x07 0xc7 0x31 0xb1 0x12 0x10 0x59 0x27 0x80 0xec 0x5f |
||||||
|
0x60 0x51 0x7f 0xa9 0x19 0xb5 0x4a 0x0d 0x2d 0xe5 0x7a 0x9f 0x93 0xc9 0x9c 0xef |
||||||
|
0xa0 0xe0 0x3b 0x4d 0xae 0x2a 0xf5 0xb0 0xc8 0xeb 0xbb 0x3c 0x83 0x53 0x99 0x61 |
||||||
|
0x17 0x2b 0x04 0x7e 0xba 0x77 0xd6 0x26 0xe1 0x69 0x14 0x63 0x55 0x21 0x0c 0x7d |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# aes::Init -- |
||||||
|
# |
||||||
|
# Initialise our AES state and calculate the key schedule. An initialization |
||||||
|
# vector is maintained in the state for modes that require one. The key must |
||||||
|
# be binary data of the correct size and the IV must be 16 bytes. |
||||||
|
# |
||||||
|
# Nk: columns of the key-array |
||||||
|
# Nr: number of rounds (depends on key-length) |
||||||
|
# Nb: columns of the text-block, is always 4 in AES |
||||||
|
# |
||||||
|
proc ::aes::Init {mode key iv} { |
||||||
|
switch -exact -- $mode { |
||||||
|
ecb - cbc { } |
||||||
|
cfb - ofb { |
||||||
|
return -code error "$mode mode not implemented" |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "invalid mode \"$mode\":\ |
||||||
|
must be one of ecb or cbc." |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set size [expr {[string length $key] << 3}] |
||||||
|
switch -exact -- $size { |
||||||
|
128 {set Nk 4; set Nr 10; set Nb 4} |
||||||
|
192 {set Nk 6; set Nr 12; set Nb 4} |
||||||
|
256 {set Nk 8; set Nr 14; set Nb 4} |
||||||
|
default { |
||||||
|
return -code error "invalid key size \"$size\":\ |
||||||
|
must be one of 128, 192 or 256." |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
variable uid |
||||||
|
set Key [namespace current]::[incr uid] |
||||||
|
upvar #0 $Key state |
||||||
|
if {[binary scan $iv Iu4 state(I)] != 1} { |
||||||
|
return -code error "invalid initialization vector: must be 16 bytes" |
||||||
|
} |
||||||
|
array set state [list M $mode K $key Nk $Nk Nr $Nr Nb $Nb W {}] |
||||||
|
ExpandKey $Key |
||||||
|
return $Key |
||||||
|
} |
||||||
|
|
||||||
|
# aes::Reset -- |
||||||
|
# |
||||||
|
# Reset the initialization vector for the specified key. This permits the |
||||||
|
# key to be reused for encryption or decryption without the expense of |
||||||
|
# re-calculating the key schedule. |
||||||
|
# |
||||||
|
proc ::aes::Reset {Key iv} { |
||||||
|
upvar #0 $Key state |
||||||
|
if {[binary scan $iv Iu4 state(I)] != 1} { |
||||||
|
return -code error "invalid initialization vector: must be 16 bytes" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# aes::Final -- |
||||||
|
# |
||||||
|
# Clean up the key state |
||||||
|
# |
||||||
|
proc ::aes::Final {Key} { |
||||||
|
# FRINK: nocheck |
||||||
|
unset $Key |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# 5.1 Cipher: Encipher a single block of 128 bits. |
||||||
|
proc ::aes::EncryptBlock {Key block} { |
||||||
|
upvar #0 $Key state |
||||||
|
if {[binary scan $block Iu4 data] != 1} { |
||||||
|
return -code error "invalid block size: blocks must be 16 bytes" |
||||||
|
} |
||||||
|
|
||||||
|
if {$state(M) eq {cbc}} { |
||||||
|
# Loop unrolled. |
||||||
|
lassign $data d0 d1 d2 d3 |
||||||
|
lassign $state(I) s0 s1 s2 s3 |
||||||
|
set data [list \ |
||||||
|
[expr {$d0 ^ $s0}] \ |
||||||
|
[expr {$d1 ^ $s1}] \ |
||||||
|
[expr {$d2 ^ $s2}] \ |
||||||
|
[expr {$d3 ^ $s3}] ] |
||||||
|
} |
||||||
|
|
||||||
|
set data [AddRoundKey $Key 0 $data] |
||||||
|
for {set n 1} {$n < $state(Nr)} {incr n} { |
||||||
|
set data [AddRoundKey $Key $n [MixColumns [ShiftRows [SubBytes $data]]]] |
||||||
|
} |
||||||
|
set data [AddRoundKey $Key $n [ShiftRows [SubBytes $data]]] |
||||||
|
|
||||||
|
# Bug 2993029: |
||||||
|
# Force all elements of data into the 32bit range. |
||||||
|
# Loop unrolled |
||||||
|
set res [Clamp32 $data] |
||||||
|
|
||||||
|
set state(I) $res |
||||||
|
binary format Iu4 $res |
||||||
|
} |
||||||
|
|
||||||
|
# 5.3: Inverse Cipher: Decipher a single 128 bit block. |
||||||
|
proc ::aes::DecryptBlock {Key block} { |
||||||
|
upvar #0 $Key state |
||||||
|
if {[binary scan $block Iu4 data] != 1} { |
||||||
|
return -code error "invalid block size: block must be 16 bytes" |
||||||
|
} |
||||||
|
set iv $data |
||||||
|
|
||||||
|
set n $state(Nr) |
||||||
|
set data [AddRoundKey $Key $state(Nr) $data] |
||||||
|
for {incr n -1} {$n > 0} {incr n -1} { |
||||||
|
set data [InvMixColumns [AddRoundKey $Key $n [InvSubBytes [InvShiftRows $data]]]] |
||||||
|
} |
||||||
|
set data [AddRoundKey $Key $n [InvSubBytes [InvShiftRows $data]]] |
||||||
|
|
||||||
|
if {$state(M) eq {cbc}} { |
||||||
|
lassign $data d0 d1 d2 d3 |
||||||
|
lassign $state(I) s0 s1 s2 s3 |
||||||
|
set data [list \ |
||||||
|
[expr {($d0 ^ $s0) & 0xffffffff}] \ |
||||||
|
[expr {($d1 ^ $s1) & 0xffffffff}] \ |
||||||
|
[expr {($d2 ^ $s2) & 0xffffffff}] \ |
||||||
|
[expr {($d3 ^ $s3) & 0xffffffff}] ] |
||||||
|
} else { |
||||||
|
# Bug 2993029: |
||||||
|
# The integrated clamping we see above only happens for CBC mode. |
||||||
|
set data [Clamp32 $data] |
||||||
|
} |
||||||
|
|
||||||
|
set state(I) $iv |
||||||
|
binary format Iu4 $data |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::Clamp32 {data} { |
||||||
|
# Force all elements into 32bit range. |
||||||
|
lassign $data d0 d1 d2 d3 |
||||||
|
list \ |
||||||
|
[expr {$d0 & 0xffffffff}] \ |
||||||
|
[expr {$d1 & 0xffffffff}] \ |
||||||
|
[expr {$d2 & 0xffffffff}] \ |
||||||
|
[expr {$d3 & 0xffffffff}] |
||||||
|
} |
||||||
|
|
||||||
|
# 5.2: KeyExpansion |
||||||
|
proc ::aes::ExpandKey {Key} { |
||||||
|
upvar #0 $Key state |
||||||
|
set Rcon [list 0x00000000 0x01000000 0x02000000 0x04000000 0x08000000 \ |
||||||
|
0x10000000 0x20000000 0x40000000 0x80000000 0x1b000000 \ |
||||||
|
0x36000000 0x6c000000 0xd8000000 0xab000000 0x4d000000] |
||||||
|
# Split the key into Nk big-endian words |
||||||
|
binary scan $state(K) I* W |
||||||
|
set max [expr {$state(Nb) * ($state(Nr) + 1)}] |
||||||
|
set i $state(Nk) |
||||||
|
set h [expr {$i - 1}] |
||||||
|
set j 0 |
||||||
|
for {} {$i < $max} {incr i; incr h; incr j} { |
||||||
|
set temp [lindex $W $h] |
||||||
|
if {($i % $state(Nk)) == 0} { |
||||||
|
set sub [SubWord [RotWord $temp]] |
||||||
|
set rc [lindex $Rcon [expr {$i/$state(Nk)}]] |
||||||
|
set temp [expr {$sub ^ $rc}] |
||||||
|
} elseif {$state(Nk) > 6 && ($i % $state(Nk)) == 4} { |
||||||
|
set temp [SubWord $temp] |
||||||
|
} |
||||||
|
lappend W [expr {[lindex $W $j] ^ $temp}] |
||||||
|
} |
||||||
|
set state(W) $W |
||||||
|
} |
||||||
|
|
||||||
|
# 5.2: Key Expansion: Apply S-box to each byte in the 32 bit word |
||||||
|
proc ::aes::SubWord {w} { |
||||||
|
variable sbox |
||||||
|
set s3 [lindex $sbox [expr {($w >> 24) & 255}]] |
||||||
|
set s2 [lindex $sbox [expr {($w >> 16) & 255}]] |
||||||
|
set s1 [lindex $sbox [expr {($w >> 8 ) & 255}]] |
||||||
|
set s0 [lindex $sbox [expr { $w & 255}]] |
||||||
|
return [expr {($s3 << 24) | ($s2 << 16) | ($s1 << 8) | $s0}] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::InvSubWord {w} { |
||||||
|
variable xobs |
||||||
|
set s3 [lindex $xobs [expr {($w >> 24) & 255}]] |
||||||
|
set s2 [lindex $xobs [expr {($w >> 16) & 255}]] |
||||||
|
set s1 [lindex $xobs [expr {($w >> 8 ) & 255}]] |
||||||
|
set s0 [lindex $xobs [expr { $w & 255}]] |
||||||
|
return [expr {($s3 << 24) | ($s2 << 16) | ($s1 << 8) | $s0}] |
||||||
|
} |
||||||
|
|
||||||
|
# 5.2: Key Expansion: Rotate a 32bit word by 8 bits |
||||||
|
proc ::aes::RotWord {w} { |
||||||
|
return [expr {(($w << 8) | (($w >> 24) & 0xff)) & 0xffffffff}] |
||||||
|
} |
||||||
|
|
||||||
|
# 5.1.1: SubBytes() Transformation |
||||||
|
proc ::aes::SubBytes {words} { |
||||||
|
lassign $words w0 w1 w2 w3 |
||||||
|
list [SubWord $w0] [SubWord $w1] [SubWord $w2] [SubWord $w3] |
||||||
|
} |
||||||
|
|
||||||
|
# 5.3.2: InvSubBytes() Transformation |
||||||
|
proc ::aes::InvSubBytes {words} { |
||||||
|
lassign $words w0 w1 w2 w3 |
||||||
|
list [InvSubWord $w0] [InvSubWord $w1] [InvSubWord $w2] [InvSubWord $w3] |
||||||
|
} |
||||||
|
|
||||||
|
# 5.1.2: ShiftRows() Transformation |
||||||
|
proc ::aes::ShiftRows {words} { |
||||||
|
for {set n0 0} {$n0 < 4} {incr n0} { |
||||||
|
set n1 [expr {($n0 + 1) % 4}] |
||||||
|
set n2 [expr {($n0 + 2) % 4}] |
||||||
|
set n3 [expr {($n0 + 3) % 4}] |
||||||
|
lappend r [expr {( [lindex $words $n0] & 0xff000000) |
||||||
|
| ([lindex $words $n1] & 0x00ff0000) |
||||||
|
| ([lindex $words $n2] & 0x0000ff00) |
||||||
|
| ([lindex $words $n3] & 0x000000ff) |
||||||
|
}] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# 5.3.1: InvShiftRows() Transformation |
||||||
|
proc ::aes::InvShiftRows {words} { |
||||||
|
for {set n0 0} {$n0 < 4} {incr n0} { |
||||||
|
set n1 [expr {($n0 + 1) % 4}] |
||||||
|
set n2 [expr {($n0 + 2) % 4}] |
||||||
|
set n3 [expr {($n0 + 3) % 4}] |
||||||
|
lappend r [expr {( [lindex $words $n0] & 0xff000000) |
||||||
|
| ([lindex $words $n3] & 0x00ff0000) |
||||||
|
| ([lindex $words $n2] & 0x0000ff00) |
||||||
|
| ([lindex $words $n1] & 0x000000ff) |
||||||
|
}] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# 5.1.3: MixColumns() Transformation |
||||||
|
proc ::aes::MixColumns {words} { |
||||||
|
set r {} |
||||||
|
foreach w $words { |
||||||
|
set r0 [expr {(($w >> 24) & 255)}] |
||||||
|
set r1 [expr {(($w >> 16) & 255)}] |
||||||
|
set r2 [expr {(($w >> 8 ) & 255)}] |
||||||
|
set r3 [expr {( $w & 255)}] |
||||||
|
|
||||||
|
set s0 [expr {[GFMult2 $r0] ^ [GFMult3 $r1] ^ $r2 ^ $r3}] |
||||||
|
set s1 [expr {$r0 ^ [GFMult2 $r1] ^ [GFMult3 $r2] ^ $r3}] |
||||||
|
set s2 [expr {$r0 ^ $r1 ^ [GFMult2 $r2] ^ [GFMult3 $r3]}] |
||||||
|
set s3 [expr {[GFMult3 $r0] ^ $r1 ^ $r2 ^ [GFMult2 $r3]}] |
||||||
|
|
||||||
|
lappend r [expr {($s0 << 24) | ($s1 << 16) | ($s2 << 8) | $s3}] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# 5.3.3: InvMixColumns() Transformation |
||||||
|
proc ::aes::InvMixColumns {words} { |
||||||
|
set r {} |
||||||
|
foreach w $words { |
||||||
|
set r0 [expr {(($w >> 24) & 255)}] |
||||||
|
set r1 [expr {(($w >> 16) & 255)}] |
||||||
|
set r2 [expr {(($w >> 8 ) & 255)}] |
||||||
|
set r3 [expr {( $w & 255)}] |
||||||
|
|
||||||
|
set s0 [expr {[GFMult0e $r0] ^ [GFMult0b $r1] ^ [GFMult0d $r2] ^ [GFMult09 $r3]}] |
||||||
|
set s1 [expr {[GFMult09 $r0] ^ [GFMult0e $r1] ^ [GFMult0b $r2] ^ [GFMult0d $r3]}] |
||||||
|
set s2 [expr {[GFMult0d $r0] ^ [GFMult09 $r1] ^ [GFMult0e $r2] ^ [GFMult0b $r3]}] |
||||||
|
set s3 [expr {[GFMult0b $r0] ^ [GFMult0d $r1] ^ [GFMult09 $r2] ^ [GFMult0e $r3]}] |
||||||
|
|
||||||
|
lappend r [expr {($s0 << 24) | ($s1 << 16) | ($s2 << 8) | $s3}] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# 5.1.4: AddRoundKey() Transformation |
||||||
|
proc ::aes::AddRoundKey {Key round words} { |
||||||
|
upvar #0 $Key state |
||||||
|
set r {} |
||||||
|
set n [expr {$round * $state(Nb)}] |
||||||
|
foreach w $words { |
||||||
|
lappend r [expr {$w ^ [lindex $state(W) $n]}] |
||||||
|
incr n |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# ::aes::GFMult* |
||||||
|
# |
||||||
|
# some needed functions for multiplication in a Galois-field |
||||||
|
# |
||||||
|
proc ::aes::GFMult2 {number} { |
||||||
|
# this is a tabular representation of xtime (multiplication by 2) |
||||||
|
# it is used instead of calculation to prevent timing attacks |
||||||
|
set xtime { |
||||||
|
0x00 0x02 0x04 0x06 0x08 0x0a 0x0c 0x0e 0x10 0x12 0x14 0x16 0x18 0x1a 0x1c 0x1e |
||||||
|
0x20 0x22 0x24 0x26 0x28 0x2a 0x2c 0x2e 0x30 0x32 0x34 0x36 0x38 0x3a 0x3c 0x3e |
||||||
|
0x40 0x42 0x44 0x46 0x48 0x4a 0x4c 0x4e 0x50 0x52 0x54 0x56 0x58 0x5a 0x5c 0x5e |
||||||
|
0x60 0x62 0x64 0x66 0x68 0x6a 0x6c 0x6e 0x70 0x72 0x74 0x76 0x78 0x7a 0x7c 0x7e |
||||||
|
0x80 0x82 0x84 0x86 0x88 0x8a 0x8c 0x8e 0x90 0x92 0x94 0x96 0x98 0x9a 0x9c 0x9e |
||||||
|
0xa0 0xa2 0xa4 0xa6 0xa8 0xaa 0xac 0xae 0xb0 0xb2 0xb4 0xb6 0xb8 0xba 0xbc 0xbe |
||||||
|
0xc0 0xc2 0xc4 0xc6 0xc8 0xca 0xcc 0xce 0xd0 0xd2 0xd4 0xd6 0xd8 0xda 0xdc 0xde |
||||||
|
0xe0 0xe2 0xe4 0xe6 0xe8 0xea 0xec 0xee 0xf0 0xf2 0xf4 0xf6 0xf8 0xfa 0xfc 0xfe |
||||||
|
0x1b 0x19 0x1f 0x1d 0x13 0x11 0x17 0x15 0x0b 0x09 0x0f 0x0d 0x03 0x01 0x07 0x05 |
||||||
|
0x3b 0x39 0x3f 0x3d 0x33 0x31 0x37 0x35 0x2b 0x29 0x2f 0x2d 0x23 0x21 0x27 0x25 |
||||||
|
0x5b 0x59 0x5f 0x5d 0x53 0x51 0x57 0x55 0x4b 0x49 0x4f 0x4d 0x43 0x41 0x47 0x45 |
||||||
|
0x7b 0x79 0x7f 0x7d 0x73 0x71 0x77 0x75 0x6b 0x69 0x6f 0x6d 0x63 0x61 0x67 0x65 |
||||||
|
0x9b 0x99 0x9f 0x9d 0x93 0x91 0x97 0x95 0x8b 0x89 0x8f 0x8d 0x83 0x81 0x87 0x85 |
||||||
|
0xbb 0xb9 0xbf 0xbd 0xb3 0xb1 0xb7 0xb5 0xab 0xa9 0xaf 0xad 0xa3 0xa1 0xa7 0xa5 |
||||||
|
0xdb 0xd9 0xdf 0xdd 0xd3 0xd1 0xd7 0xd5 0xcb 0xc9 0xcf 0xcd 0xc3 0xc1 0xc7 0xc5 |
||||||
|
0xfb 0xf9 0xff 0xfd 0xf3 0xf1 0xf7 0xf5 0xeb 0xe9 0xef 0xed 0xe3 0xe1 0xe7 0xe5 |
||||||
|
} |
||||||
|
lindex $xtime $number |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::GFMult3 {number} { |
||||||
|
# multliply by 2 (via GFMult2) and add the number again on the result (via XOR) |
||||||
|
expr {$number ^ [GFMult2 $number]} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::GFMult09 {number} { |
||||||
|
# 09 is: (02*02*02) + 01 |
||||||
|
expr {[GFMult2 [GFMult2 [GFMult2 $number]]] ^ $number} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::GFMult0b {number} { |
||||||
|
# 0b is: (02*02*02) + 02 + 01 |
||||||
|
#return [expr [GFMult2 [GFMult2 [GFMult2 $number]]] ^ [GFMult2 $number] ^ $number] |
||||||
|
#set g0 [GFMult2 $number] |
||||||
|
expr {[GFMult09 $number] ^ [GFMult2 $number]} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::GFMult0d {number} { |
||||||
|
# 0d is: (02*02*02) + (02*02) + 01 |
||||||
|
set temp [GFMult2 [GFMult2 $number]] |
||||||
|
expr {[GFMult2 $temp] ^ ($temp ^ $number)} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::GFMult0e {number} { |
||||||
|
# 0e is: (02*02*02) + (02*02) + 02 |
||||||
|
set temp [GFMult2 [GFMult2 $number]] |
||||||
|
expr {[GFMult2 $temp] ^ ($temp ^ [GFMult2 $number])} |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# aes::Encrypt -- |
||||||
|
# |
||||||
|
# Encrypt a blocks of plain text and returns blocks of cipher text. |
||||||
|
# The input data must be a multiple of the block size (16). |
||||||
|
# |
||||||
|
proc ::aes::Encrypt {Key data} { |
||||||
|
set len [string length $data] |
||||||
|
if {($len % 16) != 0} { |
||||||
|
return -code error "invalid block size: AES requires 16 byte blocks" |
||||||
|
} |
||||||
|
|
||||||
|
set result {} |
||||||
|
for {set i 0} {$i < $len} {incr i 1} { |
||||||
|
set block [string range $data $i [incr i 15]] |
||||||
|
append result [EncryptBlock $Key $block] |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# aes::Decrypt -- |
||||||
|
# |
||||||
|
# Decrypt blocks of cipher text and returns blocks of plain text. |
||||||
|
# The input data must be a multiple of the block size (16). |
||||||
|
# |
||||||
|
proc ::aes::Decrypt {Key data} { |
||||||
|
set len [string length $data] |
||||||
|
if {($len % 16) != 0} { |
||||||
|
return -code error "invalid block size: AES requires 16 byte blocks" |
||||||
|
} |
||||||
|
|
||||||
|
set result {} |
||||||
|
for {set i 0} {$i < $len} {incr i 1} { |
||||||
|
set block [string range $data $i [incr i 15]] |
||||||
|
append result [DecryptBlock $Key $block] |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# chan event handler for chunked file reading. |
||||||
|
# |
||||||
|
proc ::aes::Chunk {Key in {out {}} {chunksize 4096}} { |
||||||
|
upvar #0 $Key state |
||||||
|
|
||||||
|
#puts ||CHUNK.X||i=$in|o=$out|c=$chunksize|eof=[eof $in] |
||||||
|
|
||||||
|
if {[eof $in]} { |
||||||
|
chan event $in readable {} |
||||||
|
set state(reading) 0 |
||||||
|
} |
||||||
|
|
||||||
|
set data [read $in $chunksize] |
||||||
|
|
||||||
|
#puts ||CHUNK.R||i=$in|o=$out|c=$chunksize|eof=[eof $in]||[string length $data]||$data|| |
||||||
|
|
||||||
|
# Do nothing when data was read at all. |
||||||
|
if {$data eq {}} return |
||||||
|
|
||||||
|
if {[eof $in]} { |
||||||
|
#puts CHUNK.Z |
||||||
|
set data [Pad $data 16] |
||||||
|
} |
||||||
|
|
||||||
|
#puts ||CHUNK.P||i=$in|o=$out|c=$chunksize|eof=[eof $in]||[string length $data]||$data|| |
||||||
|
|
||||||
|
if {$out eq {}} { |
||||||
|
append state(output) [$state(cmd) $Key $data] |
||||||
|
} else { |
||||||
|
puts -nonewline $out [$state(cmd) $Key $data] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::SetOneOf {lst item} { |
||||||
|
set ndx [lsearch -glob $lst "${item}*"] |
||||||
|
if {$ndx == -1} { |
||||||
|
set err [join $lst ", "] |
||||||
|
return -code error "invalid mode \"$item\": must be one of $err" |
||||||
|
} |
||||||
|
lindex $lst $ndx |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::CheckSize {what size thing} { |
||||||
|
if {[string length $thing] != $size} { |
||||||
|
return -code error "invalid value for $what: must be $size bytes long" |
||||||
|
} |
||||||
|
return $thing |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::Pad {data blocksize {fill \0}} { |
||||||
|
set len [string length $data] |
||||||
|
if {$len == 0} { |
||||||
|
set data [string repeat $fill $blocksize] |
||||||
|
} elseif {($len % $blocksize) != 0} { |
||||||
|
set pad [expr {$blocksize - ($len % $blocksize)}] |
||||||
|
append data [string repeat $fill $pad] |
||||||
|
} |
||||||
|
return $data |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::Pop {varname {nth 0}} { |
||||||
|
upvar 1 $varname args |
||||||
|
set r [lindex $args $nth] |
||||||
|
set args [lreplace $args $nth $nth] |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
proc ::aes::aes {args} { |
||||||
|
array set opts {-dir encrypt -mode cbc -key {} -in {} -out {} -chunksize 4096 -hex 0} |
||||||
|
set opts(-iv) [string repeat \0 16] |
||||||
|
set modes {ecb cbc} |
||||||
|
set dirs {encrypt decrypt} |
||||||
|
while {([llength $args] > 1) && [string match -* [set option [lindex $args 0]]]} { |
||||||
|
switch -exact -- $option { |
||||||
|
-mode { set opts(-mode) [SetOneOf $modes [Pop args 1]] } |
||||||
|
-dir { set opts(-dir) [SetOneOf $dirs [Pop args 1]] } |
||||||
|
-iv { set opts(-iv) [CheckSize -iv 16 [Pop args 1]] } |
||||||
|
-key { set opts(-key) [Pop args 1] } |
||||||
|
-in { set opts(-in) [Pop args 1] } |
||||||
|
-out { set opts(-out) [Pop args 1] } |
||||||
|
-chunksize { set opts(-chunksize) [Pop args 1] } |
||||||
|
-hex { set opts(-hex) 1 } |
||||||
|
-- { Pop args ; break } |
||||||
|
default { |
||||||
|
set err [join [lsort [array names opts]] ", "] |
||||||
|
return -code error "bad option \"$option\":\ |
||||||
|
must be one of $err" |
||||||
|
} |
||||||
|
} |
||||||
|
Pop args |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-key) eq {}} { |
||||||
|
return -code error "no key provided: the -key option is required" |
||||||
|
} |
||||||
|
|
||||||
|
set r {} |
||||||
|
if {$opts(-in) eq {}} { |
||||||
|
|
||||||
|
if {[llength $args] != 1} { |
||||||
|
return -code error "wrong \# args:\ |
||||||
|
should be \"aes ?options...? -key keydata plaintext\"" |
||||||
|
} |
||||||
|
|
||||||
|
set data [Pad [lindex $args 0] 16] |
||||||
|
set Key [Init $opts(-mode) $opts(-key) $opts(-iv)] |
||||||
|
if {[string equal $opts(-dir) "encrypt"]} { |
||||||
|
set r [Encrypt $Key $data] |
||||||
|
} else { |
||||||
|
set r [Decrypt $Key $data] |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-out) ne {}} { |
||||||
|
puts -nonewline $opts(-out) $r |
||||||
|
set r {} |
||||||
|
} |
||||||
|
Final $Key |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
if {[llength $args] != 0} { |
||||||
|
return -code error "wrong \# args:\ |
||||||
|
should be \"aes ?options...? -key keydata -in channel\"" |
||||||
|
} |
||||||
|
|
||||||
|
set Key [Init $opts(-mode) $opts(-key) $opts(-iv)] |
||||||
|
|
||||||
|
set readcmd [list [namespace origin Chunk] \ |
||||||
|
$Key $opts(-in) $opts(-out) \ |
||||||
|
$opts(-chunksize)] |
||||||
|
|
||||||
|
upvar 1 $Key state |
||||||
|
set state(reading) 1 |
||||||
|
if {[string equal $opts(-dir) "encrypt"]} { |
||||||
|
set state(cmd) Encrypt |
||||||
|
} else { |
||||||
|
set state(cmd) Decrypt |
||||||
|
} |
||||||
|
set state(output) "" |
||||||
|
chan event $opts(-in) readable $readcmd |
||||||
|
if {[info commands ::tkwait] != {}} { |
||||||
|
tkwait variable [subst $Key](reading) |
||||||
|
} else { |
||||||
|
vwait [subst $Key](reading) |
||||||
|
} |
||||||
|
if {$opts(-out) == {}} { |
||||||
|
set r $state(output) |
||||||
|
} |
||||||
|
Final $Key |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-hex)} { |
||||||
|
binary scan $r H* r |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
package provide aes 1.2.2 |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# Local variables: |
||||||
|
# mode: tcl |
||||||
|
# indent-tabs-mode: nil |
||||||
|
# End: |
@ -0,0 +1,5 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} { |
||||||
|
# PRAGMA: returnok |
||||||
|
return |
||||||
|
} |
||||||
|
package ifneeded aes 1.2.2 [list source [file join $dir aes.tcl]] |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,9 @@ |
|||||||
|
# pkgIndex.tcl -- |
||||||
|
# Copyright (c) 2006 Darren New |
||||||
|
# This is for the Amazon S3 web service packages. |
||||||
|
|
||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
|
||||||
|
package ifneeded xsxp 1.1 [list source [file join $dir xsxp.tcl]] |
||||||
|
package ifneeded S3 1.0.5 [list source [file join $dir S3.tcl]] |
||||||
|
|
@ -0,0 +1,254 @@ |
|||||||
|
# xsxp.tcl -- |
||||||
|
# |
||||||
|
###Abstract |
||||||
|
# Extremely Simple XML Parser |
||||||
|
# |
||||||
|
# This is pretty lame, but I needed something like this for S3, |
||||||
|
# and at the time, TclDOM would not work with the new 8.5 Tcl |
||||||
|
# due to version number problems. |
||||||
|
# |
||||||
|
# In addition, this is a pure-value implementation. There is no |
||||||
|
# garbage to clean up in the event of a thrown error, for example. |
||||||
|
# This simplifies the code for sufficiently small XML documents, |
||||||
|
# which is what Amazon's S3 guarantees. |
||||||
|
# |
||||||
|
###Copyright |
||||||
|
# Copyright (c) 2006 Darren New. |
||||||
|
# All Rights Reserved. |
||||||
|
# NO WARRANTIES OF ANY TYPE ARE PROVIDED. |
||||||
|
# COPYING OR USE INDEMNIFIES THE AUTHOR IN ALL WAYS. |
||||||
|
# See the license terms in LICENSE.txt |
||||||
|
# |
||||||
|
###Revision String |
||||||
|
# SCCS: %Z% %M% %I% %E% %U% |
||||||
|
|
||||||
|
# xsxp::parse $xml |
||||||
|
# Returns a parsed XML, or PXML. A pxml is a list. |
||||||
|
# The first element is the name of the tag. |
||||||
|
# The second element is a list of name/value pairs of the |
||||||
|
# associated attribues, if any. |
||||||
|
# The third thru final values are recursively PXML values. |
||||||
|
# If the first element (element zero, that is) is "%PCDATA", |
||||||
|
# then the attributes will be emtpy and the third element |
||||||
|
# will be the text of the element. |
||||||
|
|
||||||
|
# xsxp::fetch $pxml $path ?$part? |
||||||
|
# $pxml is a parsed XML, as returned from xsxp::parse. |
||||||
|
# $path is a list of elements. Each element is the name of |
||||||
|
# a child to look up, optionally followed by a hash ("#") |
||||||
|
# and a string of digits. An emtpy list or an initial empty |
||||||
|
# element selects $pxml. If no hash sign is present, the |
||||||
|
# behavior is as if "#0" had been appended to that element. |
||||||
|
# An element of $path scans the children at the indicated |
||||||
|
# level for the n'th instance of a child whose tag matches |
||||||
|
# the part of the element before the hash sign. If an element |
||||||
|
# is simply "#" followed by digits, that indexed child is |
||||||
|
# selected, regardless of the tags in the children. So |
||||||
|
# an element of #3 will always select the fourth child |
||||||
|
# of the node under consideration. |
||||||
|
# $part defaults to %ALL. It can be one of the following: |
||||||
|
# %ALL - returns the entire selected element. |
||||||
|
# %TAGNAME - returns lindex 0 of the selected element. |
||||||
|
# %ATTRIBUTES - returns lindex 1 of the selected element. |
||||||
|
# %CHILDREN - returns lrange 2 through end of the selected element, |
||||||
|
# resulting in a list of elements being returned. |
||||||
|
# %PCDATA - returns a concatenation of all the bodies of |
||||||
|
# direct children of this node whose tag is %PCDATA. |
||||||
|
# Throws an error if no such children are found. That |
||||||
|
# is, part=%PCDATA means return the textual content found |
||||||
|
# in that node but not its children nodes. |
||||||
|
# %PCDATA? - like %PCDATA, but returns an empty string if |
||||||
|
# no PCDATA is found. |
||||||
|
|
||||||
|
# xsxp::fetchall $pxml_list $path ?$part? |
||||||
|
# Iterates over each PXML in $pxml_list, selecting the indicated |
||||||
|
# path from it, building a new list with the selected data, and |
||||||
|
# returning that new list. For example, $pxml_list might be |
||||||
|
# the %CHILDREN of a particular element, and the $path and $part |
||||||
|
# might select from each child a sub-element in which we're interested. |
||||||
|
|
||||||
|
# xsxp::only $pxml $tagname |
||||||
|
# Iterates over the direct children of $pxml and selects only |
||||||
|
# those with $tagname as their tag. Returns a list of matching |
||||||
|
# elements. |
||||||
|
|
||||||
|
# xsxp::prettyprint $pxml |
||||||
|
# Outputs to stdout a nested-list notation of the parsed XML. |
||||||
|
|
||||||
|
package require xml |
||||||
|
package provide xsxp 1.1 |
||||||
|
|
||||||
|
namespace eval xsxp { |
||||||
|
|
||||||
|
variable Stack |
||||||
|
variable Cur |
||||||
|
|
||||||
|
proc Characterdatacommand {characterdata} { |
||||||
|
variable Cur |
||||||
|
# puts "characterdatacommand $characterdata" |
||||||
|
set x [list %PCDATA {} $characterdata] |
||||||
|
lappend Cur $x |
||||||
|
} |
||||||
|
|
||||||
|
proc Elementstartcommand {name attlist args} { |
||||||
|
# puts "elementstart $name {$attlist} $args" |
||||||
|
variable Stack |
||||||
|
variable Cur |
||||||
|
lappend Stack $Cur |
||||||
|
set Cur [list $name $attlist] |
||||||
|
} |
||||||
|
|
||||||
|
proc Elementendcommand {args} { |
||||||
|
# puts "elementend $args" |
||||||
|
variable Stack |
||||||
|
variable Cur |
||||||
|
set x [lindex $Stack end] |
||||||
|
lappend x $Cur |
||||||
|
set Cur $x |
||||||
|
set Stack [lrange $Stack 0 end-1] |
||||||
|
} |
||||||
|
|
||||||
|
proc parse {xml} { |
||||||
|
variable Cur |
||||||
|
variable Stack |
||||||
|
set Cur {} |
||||||
|
set Stack {} |
||||||
|
set parser [::xml::parser \ |
||||||
|
-characterdatacommand [namespace code Characterdatacommand] \ |
||||||
|
-elementstartcommand [namespace code Elementstartcommand] \ |
||||||
|
-elementendcommand [namespace code Elementendcommand] \ |
||||||
|
-ignorewhitespace 1 -final 1 |
||||||
|
] |
||||||
|
$parser parse $xml |
||||||
|
$parser free |
||||||
|
# The following line is needed because the close of the last element |
||||||
|
# appends the outermost element to the item on the top of the stack. |
||||||
|
# Since there's nothing on the top of the stack at the close of the |
||||||
|
# last element, we append the current element to an empty list. |
||||||
|
# In essence, since we don't really have a terminating condition |
||||||
|
# on the recursion, an empty stack is still treated like an element. |
||||||
|
set Cur [lindex $Cur 0] |
||||||
|
set Cur [Normalize $Cur] |
||||||
|
return $Cur |
||||||
|
} |
||||||
|
|
||||||
|
proc Normalize {pxml} { |
||||||
|
# This iterates over pxml recursively, finding entries that |
||||||
|
# start with multiple %PCDATA elements, and coalesces their |
||||||
|
# content, so if an element contains only %PCDATA, it is |
||||||
|
# guaranteed to have only one child. |
||||||
|
# Not really necessary, given definition of part=%PCDATA |
||||||
|
# However, it makes pretty-prints nicer (for AWS at least) |
||||||
|
# and ends up with smaller lists. I have no idea why they |
||||||
|
# would put quotes around an MD5 hash in hex, tho. |
||||||
|
set dupl 1 |
||||||
|
while {$dupl} { |
||||||
|
set first [lindex $pxml 2] |
||||||
|
set second [lindex $pxml 3] |
||||||
|
if {[lindex $first 0] eq "%PCDATA" && [lindex $second 0] eq "%PCDATA"} { |
||||||
|
set repl [list %PCDATA {} [lindex $first 2][lindex $second 2]] |
||||||
|
set pxml [lreplace $pxml 2 3 $repl] |
||||||
|
} else { |
||||||
|
set dupl 0 |
||||||
|
for {set i 2} {$i < [llength $pxml]} {incr i} { |
||||||
|
set pxml [lreplace $pxml $i $i [Normalize [lindex $pxml $i]]] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $pxml |
||||||
|
} |
||||||
|
|
||||||
|
proc prettyprint {pxml {chan stdout} {indent 0}} { |
||||||
|
puts -nonewline $chan [string repeat " " $indent] |
||||||
|
if {[lindex $pxml 0] eq "%PCDATA"} { |
||||||
|
puts $chan "%PCDATA: [lindex $pxml 2]" |
||||||
|
return |
||||||
|
} |
||||||
|
puts -nonewline $chan "[lindex $pxml 0]" |
||||||
|
foreach {name val} [lindex $pxml 1] { |
||||||
|
puts -nonewline $chan " $name='$val'" |
||||||
|
} |
||||||
|
puts $chan "" |
||||||
|
foreach node [lrange $pxml 2 end] { |
||||||
|
prettyprint $node $chan [expr $indent+1] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc fetch {pxml path {part %ALL}} { |
||||||
|
set path [string trim $path /] |
||||||
|
if {-1 != [string first / $path]} { |
||||||
|
set path [split $path /] |
||||||
|
} |
||||||
|
foreach element $path { |
||||||
|
if {$pxml eq ""} {return ""} |
||||||
|
foreach {tag count} [split $element #] { |
||||||
|
if {$tag ne ""} { |
||||||
|
if {$count eq ""} {set count 0} |
||||||
|
set pxml [lrange $pxml 2 end] |
||||||
|
while {0 <= $count && 0 != [llength $pxml]} { |
||||||
|
if {$tag eq [lindex $pxml 0 0]} { |
||||||
|
incr count -1 |
||||||
|
if {$count < 0} { |
||||||
|
# We're done. Go on to next element. |
||||||
|
set pxml [lindex $pxml 0] |
||||||
|
} else { |
||||||
|
# Not done yet. Throw this away. |
||||||
|
set pxml [lrange $pxml 1 end] |
||||||
|
} |
||||||
|
} else { |
||||||
|
# Not what we want. |
||||||
|
set pxml [lrange $pxml 1 end] |
||||||
|
} |
||||||
|
} |
||||||
|
} else { # tag eq "" |
||||||
|
if {$count eq ""} { |
||||||
|
# Just select whole $pxml |
||||||
|
} else { |
||||||
|
set pxml [lindex $pxml [expr {2+$count}]] |
||||||
|
} |
||||||
|
} |
||||||
|
break |
||||||
|
} ; # done the foreach [split] loop |
||||||
|
} ; # done all the elements. |
||||||
|
if {$part eq "%ALL"} {return $pxml} |
||||||
|
if {$part eq "%ATTRIBUTES"} {return [lindex $pxml 1]} |
||||||
|
if {$part eq "%TAGNAME"} {return [lindex $pxml 0]} |
||||||
|
if {$part eq "%CHILDREN"} {return [lrange $pxml 2 end]} |
||||||
|
if {$part eq "%PCDATA" || $part eq "%PCDATA?"} { |
||||||
|
set res "" ; set found 0 |
||||||
|
foreach elem [lrange $pxml 2 end] { |
||||||
|
if {"%PCDATA" eq [lindex $elem 0]} { |
||||||
|
append res [lindex $elem 2] |
||||||
|
set found 1 |
||||||
|
} |
||||||
|
} |
||||||
|
if {$found || $part eq "%PCDATA?"} { |
||||||
|
return $res |
||||||
|
} else { |
||||||
|
error "xsxp::fetch did not find requested PCDATA" |
||||||
|
} |
||||||
|
} |
||||||
|
return $pxml ; # Don't know what he's after |
||||||
|
} |
||||||
|
|
||||||
|
proc only {pxml tag} { |
||||||
|
set res {} |
||||||
|
foreach element [lrange $pxml 2 end] { |
||||||
|
if {[lindex $element 0] eq $tag} { |
||||||
|
lappend res $element |
||||||
|
} |
||||||
|
} |
||||||
|
return $res |
||||||
|
} |
||||||
|
|
||||||
|
proc fetchall {pxml_list path {part %ALL}} { |
||||||
|
set res [list] |
||||||
|
foreach pxml $pxml_list { |
||||||
|
lappend res [fetch $pxml $path $part] |
||||||
|
} |
||||||
|
return $res |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace export xsxp parse prettyprint fetch |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,4 @@ |
|||||||
|
# Tcl package index file, version 1.1 |
||||||
|
|
||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded asn 0.8.5 [list source [file join $dir asn.tcl]] |
@ -0,0 +1,180 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# This code is hereby put into the public domain. |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Overview |
||||||
|
# Base32 encoding and decoding of small strings. |
||||||
|
# |
||||||
|
# Management code for switching between Tcl and C accelerated |
||||||
|
# implementations. |
||||||
|
|
||||||
|
# @mdgen EXCLUDE: base32_c.tcl |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ::base32 {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Management of base32 std implementations. |
||||||
|
|
||||||
|
# ::base32::LoadAccelerator -- |
||||||
|
# |
||||||
|
# Loads a named implementation, if possible. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# key Name of the implementation to load. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A boolean flag. True if the implementation |
||||||
|
# was successfully loaded; and False otherwise. |
||||||
|
|
||||||
|
proc ::base32::LoadAccelerator {key} { |
||||||
|
variable accel |
||||||
|
set isok 0 |
||||||
|
switch -exact -- $key { |
||||||
|
critcl { |
||||||
|
# Critcl implementation of base32 requires Tcl 8.4. |
||||||
|
if {![package vsatisfies [package provide Tcl] 8.4]} {return 0} |
||||||
|
if {[catch {package require tcllibc}]} {return 0} |
||||||
|
set isok [llength [info commands ::base32::critcl_encode]] |
||||||
|
} |
||||||
|
tcl { |
||||||
|
variable selfdir |
||||||
|
if {[catch {source [file join $selfdir base32_tcl.tcl]}]} {return 0} |
||||||
|
set isok [llength [info commands ::base32::tcl_encode]] |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "invalid accelerator $key:\ |
||||||
|
must be one of [join [KnownImplementations] {, }]" |
||||||
|
} |
||||||
|
} |
||||||
|
set accel($key) $isok |
||||||
|
return $isok |
||||||
|
} |
||||||
|
|
||||||
|
# ::base32::SwitchTo -- |
||||||
|
# |
||||||
|
# Activates a loaded named implementation. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# key Name of the implementation to activate. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
|
||||||
|
proc ::base32::SwitchTo {key} { |
||||||
|
variable accel |
||||||
|
variable loaded |
||||||
|
|
||||||
|
if {[string equal $key $loaded]} { |
||||||
|
# No change, nothing to do. |
||||||
|
return |
||||||
|
} elseif {![string equal $key ""]} { |
||||||
|
# Validate the target implementation of the switch. |
||||||
|
|
||||||
|
if {![info exists accel($key)]} { |
||||||
|
return -code error "Unable to activate unknown implementation \"$key\"" |
||||||
|
} elseif {![info exists accel($key)] || !$accel($key)} { |
||||||
|
return -code error "Unable to activate missing implementation \"$key\"" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Deactivate the previous implementation, if there was any. |
||||||
|
|
||||||
|
if {![string equal $loaded ""]} { |
||||||
|
foreach c {encode decode} { |
||||||
|
rename ::base32::$c ::base32::${loaded}_$c |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Activate the new implementation, if there is any. |
||||||
|
|
||||||
|
if {![string equal $key ""]} { |
||||||
|
foreach c {encode decode} { |
||||||
|
rename ::base32::${key}_$c ::base32::$c |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Remember the active implementation, for deactivation by future |
||||||
|
# switches. |
||||||
|
|
||||||
|
set loaded $key |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ::base32::Implementations -- |
||||||
|
# |
||||||
|
# Determines which implementations are |
||||||
|
# present, i.e. loaded. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# None. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A list of implementation keys. |
||||||
|
|
||||||
|
proc ::base32::Implementations {} { |
||||||
|
variable accel |
||||||
|
set res {} |
||||||
|
foreach n [array names accel] { |
||||||
|
if {!$accel($n)} continue |
||||||
|
lappend res $n |
||||||
|
} |
||||||
|
return $res |
||||||
|
} |
||||||
|
|
||||||
|
# ::base32::KnownImplementations -- |
||||||
|
# |
||||||
|
# Determines which implementations are known |
||||||
|
# as possible implementations. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# None. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A list of implementation keys. In the order |
||||||
|
# of preference, most prefered first. |
||||||
|
|
||||||
|
proc ::base32::KnownImplementations {} { |
||||||
|
return {critcl tcl} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base32::Names {} { |
||||||
|
return { |
||||||
|
critcl {tcllibc based} |
||||||
|
tcl {pure Tcl} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Initialization: Data structures. |
||||||
|
|
||||||
|
namespace eval ::base32 { |
||||||
|
variable selfdir [file dirname [info script]] |
||||||
|
variable loaded {} |
||||||
|
|
||||||
|
variable accel |
||||||
|
array set accel {tcl 0 critcl 0} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Initialization: Choose an implementation, |
||||||
|
## most prefered first. Loads only one of the |
||||||
|
## possible implementations. And activates it. |
||||||
|
|
||||||
|
namespace eval ::base32 { |
||||||
|
variable e |
||||||
|
foreach e [KnownImplementations] { |
||||||
|
if {[LoadAccelerator $e]} { |
||||||
|
SwitchTo $e |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
unset e |
||||||
|
|
||||||
|
namespace export encode decode |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ready |
||||||
|
|
||||||
|
package provide base32 0.2 |
@ -0,0 +1,254 @@ |
|||||||
|
# base32c.tcl -- |
||||||
|
# |
||||||
|
# Implementation of a base32 (std) de/encoder for Tcl. |
||||||
|
# |
||||||
|
# Public domain |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: base32_c.tcl,v 1.3 2008/01/28 22:58:18 andreas_kupries Exp $ |
||||||
|
|
||||||
|
package require critcl |
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ::base32 { |
||||||
|
# Supporting code for the main command. |
||||||
|
catch { |
||||||
|
#critcl::cheaders -g |
||||||
|
#critcl::debug memory symbols |
||||||
|
} |
||||||
|
|
||||||
|
# Main commands, encoder & decoder |
||||||
|
|
||||||
|
critcl::ccommand critcl_encode {dummy interp objc objv} { |
||||||
|
/* Syntax -*- c -*- |
||||||
|
* critcl_encode string |
||||||
|
*/ |
||||||
|
|
||||||
|
unsigned char* buf; |
||||||
|
Tcl_Size nbuf; |
||||||
|
|
||||||
|
unsigned char* out; |
||||||
|
unsigned char* at; |
||||||
|
int nout; |
||||||
|
|
||||||
|
/* |
||||||
|
* The array used for encoding |
||||||
|
*/ /* 123456789 123456789 123456789 12 */ |
||||||
|
static const char map[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; |
||||||
|
|
||||||
|
#define USAGEE "bitstring" |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs (interp, 1, objv, USAGEE); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
buf = Tcl_GetBytesFromObj (interp, objv[1], &nbuf); /* OK tcl9 */ |
||||||
|
if (buf == NULL) return TCL_ERROR; |
||||||
|
nout = ((nbuf+4)/5)*8; |
||||||
|
out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); |
||||||
|
|
||||||
|
for (at = out; nbuf >= 5; nbuf -= 5, buf += 5) { |
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[3]>>2) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[3]<<3) | (buf[4]>>5)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[4]) ]; |
||||||
|
} |
||||||
|
if (nbuf > 0) { |
||||||
|
/* Process partials at end. */ |
||||||
|
switch (nbuf) { |
||||||
|
case 1: |
||||||
|
/* |01234567| 2, padding 6 |
||||||
|
* xxxxx |
||||||
|
* xxx 00 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[0]<<2) ]; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
case 2: /* x3/=4 */ |
||||||
|
/* |01234567|01234567| 4, padding 4 |
||||||
|
* xxxxx |
||||||
|
* xxx xx |
||||||
|
* xxxxx |
||||||
|
* x 0000 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]<<4) ]; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
case 3: |
||||||
|
/* |01234567|01234567|01234567| 5, padding 3 |
||||||
|
* xxxxx |
||||||
|
* xxx xx |
||||||
|
* xxxxx |
||||||
|
* x xxxx |
||||||
|
* xxxx 0 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[2]<<1) ]; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
case 4: |
||||||
|
/* |01234567|01234567|01234567|012334567| 7, padding 1 |
||||||
|
* xxxxx |
||||||
|
* xxx xx |
||||||
|
* xxxxx |
||||||
|
* x xxxx |
||||||
|
* xxxx |
||||||
|
* xxxxx |
||||||
|
* xxxx 0 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[3]>>2) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[3]<<3) ]; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj ((char*)out, nout)); /* OK tcl9 */ |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
critcl::ccommand critcl_decode {dummy interp objc objv} { |
||||||
|
/* Syntax -*- c -*- |
||||||
|
* critcl_decode estring |
||||||
|
*/ |
||||||
|
|
||||||
|
unsigned char* buf; |
||||||
|
Tcl_Size nbuf; |
||||||
|
|
||||||
|
unsigned char* out; |
||||||
|
unsigned char* at; |
||||||
|
unsigned char x [8]; |
||||||
|
int nout; |
||||||
|
|
||||||
|
int i, j, a, pad, nx; |
||||||
|
|
||||||
|
/* |
||||||
|
* An array for translating single base-32 characters into a value. |
||||||
|
* Disallowed input characters have a value of 64. Upper and lower |
||||||
|
* case is the same. Only 128 chars, as everything above char(127) |
||||||
|
* is 64. |
||||||
|
*/ |
||||||
|
static const char map [] = { |
||||||
|
/* \00 */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* DLE */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* SPC */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* '0' */ 64, 64, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* '@' */ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
||||||
|
/* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, |
||||||
|
/* '`' */ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
||||||
|
/* 'p' */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64 |
||||||
|
}; |
||||||
|
|
||||||
|
#define USAGED "estring" |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs (interp, 1, objv, USAGED); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
buf = (unsigned char*) Tcl_GetStringFromObj (objv[1], &nbuf); /* OK tcl9 */ |
||||||
|
|
||||||
|
if (nbuf % 8) { |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj ("Length is not a multiple of 8", -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
nout = (nbuf/8)*5 *TCL_UTF_MAX; |
||||||
|
out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); |
||||||
|
|
||||||
|
#define HIGH(x) (((x) & 0x80) != 0) |
||||||
|
#define BADC(x) ((x) == 64) |
||||||
|
#define BADCHAR(a,j) (HIGH ((a)) || BADC (x [(j)] = map [(a)])) |
||||||
|
|
||||||
|
for (pad = 0, i=0, at = out; i < nbuf; i += 8, buf += 8){ |
||||||
|
for (j=0; j < 8; j++){ |
||||||
|
a = buf [j]; |
||||||
|
|
||||||
|
if (a == '=') { |
||||||
|
x[j] = 0; |
||||||
|
pad++; |
||||||
|
continue; |
||||||
|
} else if (pad) { |
||||||
|
char msg [120]; |
||||||
|
sprintf (msg, |
||||||
|
"Invalid character at index %d: \"=\" (padding found in the middle of the input)", |
||||||
|
j-1); |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
if (BADCHAR (a,j)) { |
||||||
|
char msg [100]; |
||||||
|
sprintf (msg,"Invalid character at index %d: \"%c\"",j,a); |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
*(at++) = (x[0]<<3) | (x[1]>>2) ; |
||||||
|
*(at++) = (x[1]<<6) | (x[2]<<1) | (x[3]>>4); |
||||||
|
*(at++) = (x[3]<<4) | (x[4]>>1) ; |
||||||
|
*(at++) = (x[4]<<7) | (x[5]<<2) | (x[6]>>3); |
||||||
|
*(at++) = (x[6]<<5) | x[7] ; |
||||||
|
} |
||||||
|
|
||||||
|
if (pad) { |
||||||
|
if (pad == 1) { |
||||||
|
at -= 1; |
||||||
|
} else if (pad == 3) { |
||||||
|
at -= 2; |
||||||
|
} else if (pad == 4) { |
||||||
|
at -= 3; |
||||||
|
} else if (pad == 6) { |
||||||
|
at -= 4; |
||||||
|
} else { |
||||||
|
char msg [100]; |
||||||
|
sprintf (msg,"Invalid padding of length %d",pad); |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Tcl_SetObjResult (interp, Tcl_NewByteArrayObj (out, at-out)); /* OK tcl9 */ |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ready |
@ -0,0 +1,73 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# This code is hereby put into the public domain. |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Overview |
||||||
|
# Base32 encoding and decoding of small strings. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Notes |
||||||
|
|
||||||
|
# A binary string is split into groups of 5 bits (2^5 == 32), and each |
||||||
|
# group is converted into a printable character as is specified in RFC |
||||||
|
# 3548. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Requisites |
||||||
|
|
||||||
|
package require base32::core |
||||||
|
namespace eval ::base32 {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## API & Implementation |
||||||
|
|
||||||
|
proc ::base32::tcl_encode {bitstring} { |
||||||
|
variable forward |
||||||
|
|
||||||
|
binary scan $bitstring B* bits |
||||||
|
set len [string length $bits] |
||||||
|
set rem [expr {$len % 5}] |
||||||
|
if {$rem} {append bits =/$rem} |
||||||
|
#puts "($bitstring) => <$bits>" |
||||||
|
|
||||||
|
return [string map $forward $bits] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base32::tcl_decode {estring} { |
||||||
|
variable backward |
||||||
|
variable invalid |
||||||
|
|
||||||
|
if {![core::valid $estring $invalid msg]} { |
||||||
|
return -code error $msg |
||||||
|
} |
||||||
|
#puts "I<$estring>" |
||||||
|
#puts "M<[string map $backward $estring]>" |
||||||
|
|
||||||
|
return [binary format B* [string map $backward [string toupper $estring]]] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Data structures |
||||||
|
|
||||||
|
namespace eval ::base32 { |
||||||
|
# Initialize the maps |
||||||
|
variable forward |
||||||
|
variable backward |
||||||
|
variable invalid |
||||||
|
|
||||||
|
core::define { |
||||||
|
0 A 9 J 18 S 27 3 |
||||||
|
1 B 10 K 19 T 28 4 |
||||||
|
2 C 11 L 20 U 29 5 |
||||||
|
3 D 12 M 21 V 30 6 |
||||||
|
4 E 13 N 22 W 31 7 |
||||||
|
5 F 14 O 23 X |
||||||
|
6 G 15 P 24 Y |
||||||
|
7 H 16 Q 25 Z |
||||||
|
8 I 17 R 26 2 |
||||||
|
} forward backward invalid ; # {} |
||||||
|
# puts ///$forward/// |
||||||
|
# puts ///$backward/// |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ok |
@ -0,0 +1,134 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# This code is hereby put into the public domain. |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
#= Overview |
||||||
|
|
||||||
|
# Fundamental handling of base32 conversion tables. Expansion of a |
||||||
|
# basic mapping into a full mapping and its inverse mapping. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
#= Requisites |
||||||
|
|
||||||
|
namespace eval ::base32::core {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
#= API & Implementation |
||||||
|
|
||||||
|
proc ::base32::core::define {map fv bv iv} { |
||||||
|
variable bits |
||||||
|
upvar 1 $fv forward $bv backward $iv invalid |
||||||
|
|
||||||
|
# bytes - bits - padding - tail | bits - padding - tail |
||||||
|
# 0 - 0 - "" - "xxxxxxxx" | 0 - "" - "" |
||||||
|
# 1 - 8 - "======" - "xx======" | 3 - "======" - "x======" |
||||||
|
# 2 - 16 - "====" - "xxxx====" | 1 - "====" - "x====" |
||||||
|
# 3 - 24 - "===" - "xxxxx===" | 4 - "===" - "x===" |
||||||
|
# 4 - 32 - "=" - "xxxxxxx=" | 2 - "=" - "x=" |
||||||
|
|
||||||
|
array set _ $bits |
||||||
|
|
||||||
|
set invalid "\[^=" |
||||||
|
set forward {} |
||||||
|
set btmp {} |
||||||
|
|
||||||
|
foreach {code char} $map { |
||||||
|
set b $_($code) |
||||||
|
|
||||||
|
append invalid [string tolower $char][string toupper $char] |
||||||
|
|
||||||
|
# 5 bit remainder |
||||||
|
lappend forward $b $char |
||||||
|
lappend btmp [list $char $b] |
||||||
|
|
||||||
|
# 4 bit remainder |
||||||
|
if {$code%2} continue |
||||||
|
set b [string range $b 0 end-1] |
||||||
|
lappend forward ${b}=/4 ${char}=== |
||||||
|
lappend btmp [list ${char}=== $b] |
||||||
|
|
||||||
|
# 3 bit remainder |
||||||
|
if {$code%4} continue |
||||||
|
set b [string range $b 0 end-1] |
||||||
|
lappend forward ${b}=/3 ${char}====== |
||||||
|
lappend btmp [list ${char}====== $b] |
||||||
|
|
||||||
|
# 2 bit remainder |
||||||
|
if {$code%8} continue |
||||||
|
set b [string range $b 0 end-1] |
||||||
|
lappend forward ${b}=/2 ${char}= |
||||||
|
lappend btmp [list ${char}= $b] |
||||||
|
|
||||||
|
# 1 bit remainder |
||||||
|
if {$code%16} continue |
||||||
|
set b [string range $b 0 end-1] |
||||||
|
lappend forward ${b}=/1 ${char}==== |
||||||
|
lappend btmp [list ${char}==== $b] |
||||||
|
} |
||||||
|
|
||||||
|
set backward {} |
||||||
|
foreach item [lsort -index 0 -decreasing $btmp] { |
||||||
|
foreach {c b} $item break |
||||||
|
lappend backward $c $b |
||||||
|
} |
||||||
|
|
||||||
|
append invalid "\]" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base32::core::valid {estring pattern mv} { |
||||||
|
upvar 1 $mv message |
||||||
|
|
||||||
|
if {[string length $estring] % 8} { |
||||||
|
set message "Length is not a multiple of 8" |
||||||
|
return 0 |
||||||
|
} elseif {[regexp -indices $pattern $estring where]} { |
||||||
|
foreach {s e} $where break |
||||||
|
set message "Invalid character at index $s: \"[string index $estring $s]\"" |
||||||
|
return 0 |
||||||
|
} elseif {[regexp {(=+)$} $estring -> pad]} { |
||||||
|
set padlen [string length $pad] |
||||||
|
if { |
||||||
|
($padlen != 6) && |
||||||
|
($padlen != 4) && |
||||||
|
($padlen != 3) && |
||||||
|
($padlen != 1) |
||||||
|
} { |
||||||
|
set message "Invalid padding of length $padlen" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Remove the brackets and ^= from the pattern, to construct the |
||||||
|
# class of valid characters which must not follow the padding. |
||||||
|
|
||||||
|
set badp "=\[[string range $pattern 3 end-1]\]" |
||||||
|
if {[regexp -indices $badp $estring where]} { |
||||||
|
foreach {s e} $where break |
||||||
|
set message "Invalid character at index $s: \"[string index $estring $s]\" (padding found in the middle of the input)" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Data structures |
||||||
|
|
||||||
|
namespace eval ::base32::core { |
||||||
|
namespace export define valid |
||||||
|
|
||||||
|
variable bits { |
||||||
|
0 00000 1 00001 2 00010 3 00011 |
||||||
|
4 00100 5 00101 6 00110 7 00111 |
||||||
|
8 01000 9 01001 10 01010 11 01011 |
||||||
|
12 01100 13 01101 14 01110 15 01111 |
||||||
|
16 10000 17 10001 18 10010 19 10011 |
||||||
|
20 10100 21 10101 22 10110 23 10111 |
||||||
|
24 11000 25 11001 26 11010 27 11011 |
||||||
|
28 11100 29 11101 30 11110 31 11111 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
#= Registration |
||||||
|
|
||||||
|
package provide base32::core 0.2 |
@ -0,0 +1,182 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# This code is hereby put into the public domain. |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Overview |
||||||
|
# Base32 encoding and decoding of small strings. |
||||||
|
# |
||||||
|
# Management code for switching between Tcl and C accelerated |
||||||
|
# implementations. |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: base32hex.tcl,v 1.3 2008/03/22 23:46:42 andreas_kupries Exp $ |
||||||
|
|
||||||
|
# @mdgen EXCLUDE: base32hex_c.tcl |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ::base32::hex {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Management of base32 std implementations. |
||||||
|
|
||||||
|
# ::base32::hex::LoadAccelerator -- |
||||||
|
# |
||||||
|
# Loads a named implementation, if possible. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# key Name of the implementation to load. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A boolean flag. True if the implementation |
||||||
|
# was successfully loaded; and False otherwise. |
||||||
|
|
||||||
|
proc ::base32::hex::LoadAccelerator {key} { |
||||||
|
variable accel |
||||||
|
set isok 0 |
||||||
|
switch -exact -- $key { |
||||||
|
critcl { |
||||||
|
# Critcl implementation of base32 requires Tcl 8.4. |
||||||
|
if {![package vsatisfies [package provide Tcl] 8.4]} {return 0} |
||||||
|
if {[catch {package require tcllibc}]} {return 0} |
||||||
|
set isok [llength [info commands ::base32::hex::critcl_encode]] |
||||||
|
} |
||||||
|
tcl { |
||||||
|
variable selfdir |
||||||
|
if {[catch {source [file join $selfdir base32hex_tcl.tcl]}]} {return 0} |
||||||
|
set isok [llength [info commands ::base32::hex::tcl_encode]] |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "invalid accelerator $key:\ |
||||||
|
must be one of [join [KnownImplementations] {, }]" |
||||||
|
} |
||||||
|
} |
||||||
|
set accel($key) $isok |
||||||
|
return $isok |
||||||
|
} |
||||||
|
|
||||||
|
# ::base32::hex::SwitchTo -- |
||||||
|
# |
||||||
|
# Activates a loaded named implementation. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# key Name of the implementation to activate. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
|
||||||
|
proc ::base32::hex::SwitchTo {key} { |
||||||
|
variable accel |
||||||
|
variable loaded |
||||||
|
|
||||||
|
if {[string equal $key $loaded]} { |
||||||
|
# No change, nothing to do. |
||||||
|
return |
||||||
|
} elseif {![string equal $key ""]} { |
||||||
|
# Validate the target implementation of the switch. |
||||||
|
|
||||||
|
if {![info exists accel($key)]} { |
||||||
|
return -code error "Unable to activate unknown implementation \"$key\"" |
||||||
|
} elseif {![info exists accel($key)] || !$accel($key)} { |
||||||
|
return -code error "Unable to activate missing implementation \"$key\"" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Deactivate the previous implementation, if there was any. |
||||||
|
|
||||||
|
if {![string equal $loaded ""]} { |
||||||
|
foreach c {encode decode} { |
||||||
|
rename ::base32::hex::$c ::base32::hex::${loaded}_$c |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Activate the new implementation, if there is any. |
||||||
|
|
||||||
|
if {![string equal $key ""]} { |
||||||
|
foreach c {encode decode} { |
||||||
|
rename ::base32::hex::${key}_$c ::base32::hex::$c |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Remember the active implementation, for deactivation by future |
||||||
|
# switches. |
||||||
|
|
||||||
|
set loaded $key |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ::base32::hex::Implementations -- |
||||||
|
# |
||||||
|
# Determines which implementations are |
||||||
|
# present, i.e. loaded. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# None. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A list of implementation keys. |
||||||
|
|
||||||
|
proc ::base32::hex::Implementations {} { |
||||||
|
variable accel |
||||||
|
set res {} |
||||||
|
foreach n [array names accel] { |
||||||
|
if {!$accel($n)} continue |
||||||
|
lappend res $n |
||||||
|
} |
||||||
|
return $res |
||||||
|
} |
||||||
|
|
||||||
|
# ::base32::hex::KnownImplementations -- |
||||||
|
# |
||||||
|
# Determines which implementations are known |
||||||
|
# as possible implementations. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# None. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A list of implementation keys. In the order |
||||||
|
# of preference, most prefered first. |
||||||
|
|
||||||
|
proc ::base32::hex::KnownImplementations {} { |
||||||
|
return {critcl tcl} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base32::hex::Names {} { |
||||||
|
return { |
||||||
|
critcl {tcllibc based} |
||||||
|
tcl {pure Tcl} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Initialization: Data structures. |
||||||
|
|
||||||
|
namespace eval ::base32::hex { |
||||||
|
variable selfdir [file dirname [info script]] |
||||||
|
variable loaded {} |
||||||
|
|
||||||
|
variable accel |
||||||
|
array set accel {tcl 0 critcl 0} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Initialization: Choose an implementation, |
||||||
|
## most prefered first. Loads only one of the |
||||||
|
## possible implementations. And activates it. |
||||||
|
|
||||||
|
namespace eval ::base32::hex { |
||||||
|
variable e |
||||||
|
foreach e [KnownImplementations] { |
||||||
|
if {[LoadAccelerator $e]} { |
||||||
|
SwitchTo $e |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
unset e |
||||||
|
|
||||||
|
namespace export encode decode |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ready |
||||||
|
|
||||||
|
package provide base32::hex 0.2 |
@ -0,0 +1,254 @@ |
|||||||
|
# base32hexc.tcl -- |
||||||
|
# |
||||||
|
# Implementation of a base32 (extended hex) de/encoder for Tcl. |
||||||
|
# |
||||||
|
# Public domain |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: base32hex_c.tcl,v 1.3 2008/01/28 22:58:18 andreas_kupries Exp $ |
||||||
|
|
||||||
|
package require critcl |
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ::base32::hex { |
||||||
|
# Supporting code for the main command. |
||||||
|
catch { |
||||||
|
#critcl::cheaders -g |
||||||
|
#critcl::debug memory symbols |
||||||
|
} |
||||||
|
|
||||||
|
# Main commands, encoder & decoder |
||||||
|
|
||||||
|
critcl::ccommand critcl_encode {dummy interp objc objv} { |
||||||
|
/* Syntax -*- c -*- |
||||||
|
* critcl_encode string |
||||||
|
*/ |
||||||
|
|
||||||
|
unsigned char* buf; |
||||||
|
Tcl_Size nbuf; |
||||||
|
|
||||||
|
unsigned char* out; |
||||||
|
unsigned char* at; |
||||||
|
int nout; |
||||||
|
|
||||||
|
/* |
||||||
|
* The array used for encoding |
||||||
|
*/ /* 123456789 123456789 123456789 12 */ |
||||||
|
static const char map[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; |
||||||
|
|
||||||
|
#define USAGEE "bitstring" |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs (interp, 1, objv, USAGEE); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
buf = Tcl_GetBytesFromObj (interp, objv[1], &nbuf); /* OK tcl9 */ |
||||||
|
if (buf == NULL) return TCL_ERROR; |
||||||
|
nout = ((nbuf+4)/5)*8; |
||||||
|
out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); |
||||||
|
|
||||||
|
for (at = out; nbuf >= 5; nbuf -= 5, buf += 5) { |
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[3]>>2) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[3]<<3) | (buf[4]>>5)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[4]) ]; |
||||||
|
} |
||||||
|
if (nbuf > 0) { |
||||||
|
/* Process partials at end. */ |
||||||
|
switch (nbuf) { |
||||||
|
case 1: |
||||||
|
/* |01234567| 2, padding 6 |
||||||
|
* xxxxx |
||||||
|
* xxx 00 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[0]<<2) ]; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
case 2: /* x3/=4 */ |
||||||
|
/* |01234567|01234567| 4, padding 4 |
||||||
|
* xxxxx |
||||||
|
* xxx xx |
||||||
|
* xxxxx |
||||||
|
* x 0000 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]<<4) ]; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
case 3: |
||||||
|
/* |01234567|01234567|01234567| 5, padding 3 |
||||||
|
* xxxxx |
||||||
|
* xxx xx |
||||||
|
* xxxxx |
||||||
|
* x xxxx |
||||||
|
* xxxx 0 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[2]<<1) ]; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
case 4: |
||||||
|
/* |01234567|01234567|01234567|012334567| 7, padding 1 |
||||||
|
* xxxxx |
||||||
|
* xxx xx |
||||||
|
* xxxxx |
||||||
|
* x xxxx |
||||||
|
* xxxx |
||||||
|
* xxxxx |
||||||
|
* xxxx 0 |
||||||
|
*/ |
||||||
|
|
||||||
|
*(at++) = map [ (buf[0]>>3) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[0]<<2) | (buf[1]>>6)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[1]>>1) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[1]<<4) | (buf[2]>>4)) ]; |
||||||
|
*(at++) = map [ 0x1f & ((buf[2]<<1) | (buf[3]>>7)) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[3]>>2) ]; |
||||||
|
*(at++) = map [ 0x1f & (buf[3]<<3) ]; |
||||||
|
*(at++) = '='; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj ((char*)out, nout)); /* OK tcl9 */ |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
critcl::ccommand critcl_decode {dummy interp objc objv} { |
||||||
|
/* Syntax -*- c -*- |
||||||
|
* critcl_decode estring |
||||||
|
*/ |
||||||
|
|
||||||
|
unsigned char* buf; |
||||||
|
Tcl_Size nbuf; |
||||||
|
|
||||||
|
unsigned char* out; |
||||||
|
unsigned char* at; |
||||||
|
unsigned char x [8]; |
||||||
|
int nout; |
||||||
|
|
||||||
|
int i, j, a, pad, nx; |
||||||
|
|
||||||
|
/* |
||||||
|
* An array for translating single base-32 characters into a value. |
||||||
|
* Disallowed input characters have a value of 64. Upper and lower |
||||||
|
* case is the same. Only 128 chars, as everything above char(127) |
||||||
|
* is 64. |
||||||
|
*/ |
||||||
|
static const char map [] = { |
||||||
|
/* \00 */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* DLE */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* SPC */ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* '0' */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 64, 64, 64, 64, 64, 64, |
||||||
|
/* '@' */ 64, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, |
||||||
|
/* 'P' */ 25, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
||||||
|
/* '`' */ 64, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, |
||||||
|
/* 'p' */ 25, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, 64 |
||||||
|
}; |
||||||
|
|
||||||
|
#define USAGED "estring" |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs (interp, 1, objv, USAGED); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
buf = (unsigned char*) Tcl_GetStringFromObj (objv[1], &nbuf); /* OK tcl9 */ |
||||||
|
|
||||||
|
if (nbuf % 8) { |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj ("Length is not a multiple of 8", -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
nout = (nbuf/8)*5 *TCL_UTF_MAX; |
||||||
|
out = (unsigned char*) Tcl_Alloc (nout*sizeof(char)); |
||||||
|
|
||||||
|
#define HIGH(x) (((x) & 0x80) != 0) |
||||||
|
#define BADC(x) ((x) == 64) |
||||||
|
#define BADCHAR(a,j) (HIGH ((a)) || BADC (x [(j)] = map [(a)])) |
||||||
|
|
||||||
|
for (pad = 0, i=0, at = out; i < nbuf; i += 8, buf += 8){ |
||||||
|
for (j=0; j < 8; j++){ |
||||||
|
a = buf [j]; |
||||||
|
|
||||||
|
if (a == '=') { |
||||||
|
x[j] = 0; |
||||||
|
pad++; |
||||||
|
continue; |
||||||
|
} else if (pad) { |
||||||
|
char msg [120]; |
||||||
|
sprintf (msg, |
||||||
|
"Invalid character at index %d: \"=\" (padding found in the middle of the input)", |
||||||
|
j-1); |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
if (BADCHAR (a,j)) { |
||||||
|
char msg [100]; |
||||||
|
sprintf (msg,"Invalid character at index %d: \"%c\"",j,a); |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
*(at++) = (x[0]<<3) | (x[1]>>2) ; |
||||||
|
*(at++) = (x[1]<<6) | (x[2]<<1) | (x[3]>>4); |
||||||
|
*(at++) = (x[3]<<4) | (x[4]>>1) ; |
||||||
|
*(at++) = (x[4]<<7) | (x[5]<<2) | (x[6]>>3); |
||||||
|
*(at++) = (x[6]<<5) | x[7] ; |
||||||
|
} |
||||||
|
|
||||||
|
if (pad) { |
||||||
|
if (pad == 1) { |
||||||
|
at -= 1; |
||||||
|
} else if (pad == 3) { |
||||||
|
at -= 2; |
||||||
|
} else if (pad == 4) { |
||||||
|
at -= 3; |
||||||
|
} else if (pad == 6) { |
||||||
|
at -= 4; |
||||||
|
} else { |
||||||
|
char msg [100]; |
||||||
|
sprintf (msg,"Invalid padding of length %d",pad); |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
Tcl_SetObjResult (interp, Tcl_NewStringObj (msg, -1)); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Tcl_SetObjResult (interp, Tcl_NewByteArrayObj (out, at-out)); /* OK tcl9 */ |
||||||
|
Tcl_Free ((char*) out); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ready |
@ -0,0 +1,79 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# This code is hereby put into the public domain. |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Overview |
||||||
|
# Base32 encoding and decoding of small strings. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Notes |
||||||
|
|
||||||
|
# A binary string is split into groups of 5 bits (2^5 == 32), and each |
||||||
|
# group is converted into a printable character as is specified in RFC |
||||||
|
# 3548 for the extended hex encoding. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Requisites |
||||||
|
|
||||||
|
package require base32::core |
||||||
|
namespace eval ::base32::hex {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## API & Implementation |
||||||
|
|
||||||
|
proc ::base32::hex::tcl_encode {bitstring} { |
||||||
|
variable forward |
||||||
|
|
||||||
|
binary scan $bitstring B* bits |
||||||
|
set len [string length $bits] |
||||||
|
set rem [expr {$len % 5}] |
||||||
|
if {$rem} {append bits =/$rem} |
||||||
|
#puts "($bitstring) => <$bits>" |
||||||
|
|
||||||
|
return [string map $forward $bits] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base32::hex::tcl_decode {estring} { |
||||||
|
variable backward |
||||||
|
variable invalid |
||||||
|
|
||||||
|
if {![core::valid $estring $invalid msg]} { |
||||||
|
return -code error $msg |
||||||
|
} |
||||||
|
#puts "I<$estring>" |
||||||
|
#puts "M<[string map $backward $estring]>" |
||||||
|
|
||||||
|
return [binary format B* [string map $backward [string toupper $estring]]] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Data structures |
||||||
|
|
||||||
|
namespace eval ::base32::hex { |
||||||
|
namespace eval core { |
||||||
|
namespace import ::base32::core::define |
||||||
|
namespace import ::base32::core::valid |
||||||
|
} |
||||||
|
|
||||||
|
namespace export encode decode |
||||||
|
# Initialize the maps |
||||||
|
variable forward |
||||||
|
variable backward |
||||||
|
variable invalid |
||||||
|
|
||||||
|
core::define { |
||||||
|
0 0 9 9 18 I 27 R |
||||||
|
1 1 10 A 19 J 28 S |
||||||
|
2 2 11 B 20 K 29 T |
||||||
|
3 3 12 C 21 L 30 U |
||||||
|
4 4 13 D 22 M 31 V |
||||||
|
5 5 14 E 23 N |
||||||
|
6 6 15 F 24 O |
||||||
|
7 7 16 G 25 P |
||||||
|
8 8 17 H 26 Q |
||||||
|
} forward backward invalid ; # {} |
||||||
|
# puts ///$forward/// |
||||||
|
# puts ///$backward/// |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ok |
@ -0,0 +1,4 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} return |
||||||
|
package ifneeded base32 0.2 [list source [file join $dir base32.tcl]] |
||||||
|
package ifneeded base32::hex 0.2 [list source [file join $dir base32hex.tcl]] |
||||||
|
package ifneeded base32::core 0.2 [list source [file join $dir base32core.tcl]] |
@ -0,0 +1,270 @@ |
|||||||
|
# ascii85.tcl -- |
||||||
|
# |
||||||
|
# Encode/Decode ascii85 for a string |
||||||
|
# |
||||||
|
# Copyright (c) Emiliano Gavilan |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ascii85 { |
||||||
|
namespace export encode encodefile decode |
||||||
|
# default values for encode options |
||||||
|
variable options |
||||||
|
array set options [list -wrapchar \n -maxlen 76] |
||||||
|
} |
||||||
|
|
||||||
|
# ::ascii85::encode -- |
||||||
|
# |
||||||
|
# Ascii85 encode a given string. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# args ?-maxlen maxlen? ?-wrapchar wrapchar? string |
||||||
|
# |
||||||
|
# If maxlen is 0, the output is not wrapped. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A Ascii85 encoded version of $string, wrapped at $maxlen characters |
||||||
|
# by $wrapchar. |
||||||
|
|
||||||
|
proc ascii85::encode {args} { |
||||||
|
variable options |
||||||
|
|
||||||
|
set alen [llength $args] |
||||||
|
if {$alen != 1 && $alen != 3 && $alen != 5} { |
||||||
|
return -code error "wrong # args:\ |
||||||
|
should be \"[lindex [info level 0] 0]\ |
||||||
|
?-maxlen maxlen?\ |
||||||
|
?-wrapchar wrapchar? string\"" |
||||||
|
} |
||||||
|
|
||||||
|
set data [lindex $args end] |
||||||
|
array set opts [array get options] |
||||||
|
array set opts [lrange $args 0 end-1] |
||||||
|
foreach key [array names opts] { |
||||||
|
if {[lsearch -exact [array names options] $key] == -1} { |
||||||
|
return -code error "unknown option \"$key\":\ |
||||||
|
must be -maxlen or -wrapchar" |
||||||
|
} |
||||||
|
} |
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $opts(-maxlen)] |
||||||
|
|| $opts(-maxlen) < 0} { |
||||||
|
return -code error "expected positive integer but got\ |
||||||
|
\"$opts(-maxlen)\"" |
||||||
|
} |
||||||
|
|
||||||
|
# perform this check early |
||||||
|
if {[string length $data] == 0} { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
# shorten the names, and normalize numeric values. |
||||||
|
set ml [format %d $opts(-maxlen)] |
||||||
|
set wc $opts(-wrapchar) |
||||||
|
|
||||||
|
# if maxlen is zero, don't wrap the output |
||||||
|
if {$ml == 0} { |
||||||
|
set wc "" |
||||||
|
} |
||||||
|
|
||||||
|
set encoded {} |
||||||
|
|
||||||
|
binary scan $data c* X |
||||||
|
set len [llength $X] |
||||||
|
set rest [expr {$len % 4}] |
||||||
|
set lastidx [expr {$len - $rest - 1}] |
||||||
|
|
||||||
|
foreach {b1 b2 b3 b4} [lrange $X 0 $lastidx] { |
||||||
|
# calculate the 32 bit value |
||||||
|
# this is an inlined version of the [encode4bytes] proc |
||||||
|
# included here for performance reasons |
||||||
|
set val [expr { |
||||||
|
( (($b1 & 0xff) << 24) |
||||||
|
|(($b2 & 0xff) << 16) |
||||||
|
|(($b3 & 0xff) << 8) |
||||||
|
| ($b4 & 0xff) |
||||||
|
) & 0xffffffff }] |
||||||
|
|
||||||
|
if {$val == 0} { |
||||||
|
# four \0 bytes encodes as "z" instead of "!!!!!" |
||||||
|
append current "z" |
||||||
|
} else { |
||||||
|
# no magic numbers here. |
||||||
|
# 52200625 -> 85 ** 4 |
||||||
|
# 614125 -> 85 ** 3 |
||||||
|
# 7225 -> 85 ** 2 |
||||||
|
append current [binary format ccccc \ |
||||||
|
[expr { ( $val / 52200625) + 33 }] \ |
||||||
|
[expr { (($val % 52200625) / 614125) + 33 }] \ |
||||||
|
[expr { (($val % 614125) / 7225) + 33 }] \ |
||||||
|
[expr { (($val % 7225) / 85) + 33 }] \ |
||||||
|
[expr { ( $val % 85) + 33 }]] |
||||||
|
} |
||||||
|
|
||||||
|
if {[string length $current] >= $ml} { |
||||||
|
append encoded [string range $current 0 [expr {$ml - 1}]] $wc |
||||||
|
set current [string range $current $ml end] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if { $rest } { |
||||||
|
# there are remaining bytes. |
||||||
|
# pad with \0 and encode not using the "z" convention. |
||||||
|
# finally, add ($rest + 1) chars. |
||||||
|
set val 0 |
||||||
|
foreach {b1 b2 b3 b4} [pad [lrange $X [incr lastidx] end] 4 0] break |
||||||
|
append current [string range [encode4bytes $b1 $b2 $b3 $b4] 0 $rest] |
||||||
|
} |
||||||
|
append encoded [regsub -all -- ".{$ml}" $current "&$wc"] |
||||||
|
|
||||||
|
return $encoded |
||||||
|
} |
||||||
|
|
||||||
|
proc ascii85::encode4bytes {b1 b2 b3 b4} { |
||||||
|
set val [expr { |
||||||
|
( (($b1 & 0xff) << 24) |
||||||
|
|(($b2 & 0xff) << 16) |
||||||
|
|(($b3 & 0xff) << 8) |
||||||
|
| ($b4 & 0xff) |
||||||
|
) & 0xffffffff }] |
||||||
|
return [binary format ccccc \ |
||||||
|
[expr { ( $val / 52200625) + 33 }] \ |
||||||
|
[expr { (($val % 52200625) / 614125) + 33 }] \ |
||||||
|
[expr { (($val % 614125) / 7225) + 33 }] \ |
||||||
|
[expr { (($val % 7225) / 85) + 33 }] \ |
||||||
|
[expr { ( $val % 85) + 33 }]] |
||||||
|
} |
||||||
|
|
||||||
|
# ::ascii85::encodefile -- |
||||||
|
# |
||||||
|
# Ascii85 encode the contents of a file using default values |
||||||
|
# for maxlen and wrapchar parameters. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# fname The name of the file to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# An Ascii85 encoded version of the contents of the file. |
||||||
|
# This is a convenience command |
||||||
|
|
||||||
|
proc ascii85::encodefile {fname} { |
||||||
|
set fd [open $fname rb] |
||||||
|
return [encode [read $fd]][close $fd] |
||||||
|
} |
||||||
|
|
||||||
|
# ::ascii85::decode -- |
||||||
|
# |
||||||
|
# Ascii85 decode a given string. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# string The string to decode. |
||||||
|
# Leading spaces and tabs are removed, along with trailing newlines |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The decoded value. |
||||||
|
|
||||||
|
proc ascii85::decode {data} { |
||||||
|
# get rid of leading spaces/tabs and trailing newlines |
||||||
|
set data [string map [list \n {} \t {} { } {}] $data] |
||||||
|
set len [string length $data] |
||||||
|
|
||||||
|
# perform this ckeck early |
||||||
|
if {! $len} { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
set decoded {} |
||||||
|
set count 0 |
||||||
|
set group [list] |
||||||
|
binary scan $data c* X |
||||||
|
|
||||||
|
foreach char $X { |
||||||
|
# we must check that every char is in the allowed range |
||||||
|
if {$char < 33 || $char > 117 } { |
||||||
|
# "z" is an exception |
||||||
|
if {$char == 122} { |
||||||
|
if {$count == 0} { |
||||||
|
# if a "z" char appears at the beggining of a group, |
||||||
|
# it decodes as four null bytes |
||||||
|
append decoded \x00\x00\x00\x00 |
||||||
|
continue |
||||||
|
} else { |
||||||
|
# if not, is an error |
||||||
|
return -code error \ |
||||||
|
"error decoding data: \"z\" char misplaced" |
||||||
|
} |
||||||
|
} |
||||||
|
# char is not in range and not a "z" at the beggining of a group |
||||||
|
return -code error \ |
||||||
|
"error decoding data: chars outside the allowed range" |
||||||
|
} |
||||||
|
|
||||||
|
lappend group $char |
||||||
|
incr count |
||||||
|
if {$count == 5} { |
||||||
|
# this is an inlined version of the [decode5chars] proc |
||||||
|
# included here for performance reasons |
||||||
|
set val [expr { |
||||||
|
([lindex $group 0] - 33) * wide(52200625) + |
||||||
|
([lindex $group 1] - 33) * 614125 + |
||||||
|
([lindex $group 2] - 33) * 7225 + |
||||||
|
([lindex $group 3] - 33) * 85 + |
||||||
|
([lindex $group 4] - 33) }] |
||||||
|
if {$val > 0xffffffff} { |
||||||
|
return -code error "error decoding data: decoded group overflow" |
||||||
|
} else { |
||||||
|
append decoded [binary format I $val] |
||||||
|
incr count -5 |
||||||
|
set group [list] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set len [llength $group] |
||||||
|
switch -- $len { |
||||||
|
0 { |
||||||
|
# all input has been consumed |
||||||
|
# do nothing |
||||||
|
} |
||||||
|
1 { |
||||||
|
# a single char is a condition error, there should be at least 2 |
||||||
|
return -code error \ |
||||||
|
"error decoding data: trailing char" |
||||||
|
} |
||||||
|
default { |
||||||
|
# pad with "u"s, decode and add ($len - 1) bytes |
||||||
|
append decoded [string range \ |
||||||
|
[decode5chars [pad $group 5 122]] \ |
||||||
|
0 \ |
||||||
|
[expr {$len - 2}]] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $decoded |
||||||
|
} |
||||||
|
|
||||||
|
proc ascii85::decode5chars {group} { |
||||||
|
set val [expr { |
||||||
|
([lindex $group 0] - 33) * wide(52200625) + |
||||||
|
([lindex $group 1] - 33) * 614125 + |
||||||
|
([lindex $group 2] - 33) * 7225 + |
||||||
|
([lindex $group 3] - 33) * 85 + |
||||||
|
([lindex $group 4] - 33) }] |
||||||
|
if {$val > 0xffffffff} { |
||||||
|
return -code error "error decoding data: decoded group overflow" |
||||||
|
} |
||||||
|
|
||||||
|
return [binary format I $val] |
||||||
|
} |
||||||
|
|
||||||
|
proc ascii85::pad {chars len padchar} { |
||||||
|
while {[llength $chars] < $len} { |
||||||
|
lappend chars $padchar |
||||||
|
} |
||||||
|
|
||||||
|
return $chars |
||||||
|
} |
||||||
|
|
||||||
|
package provide ascii85 1.1.1 |
@ -0,0 +1,411 @@ |
|||||||
|
# base64.tcl -- |
||||||
|
# |
||||||
|
# Encode/Decode base64 for a string |
||||||
|
# Stephen Uhler / Brent Welch (c) 1997 Sun Microsystems |
||||||
|
# The decoder was done for exmh by Chris Garrigues |
||||||
|
# |
||||||
|
# Copyright (c) 1998-2000 by Ajuba Solutions. |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
|
||||||
|
# Version 1.0 implemented Base64_Encode, Base64_Decode |
||||||
|
# Version 2.0 uses the base64 namespace |
||||||
|
# Version 2.1 fixes various decode bugs and adds options to encode |
||||||
|
# Version 2.2 is much faster, Tcl8.0 compatible |
||||||
|
# Version 2.2.1 bugfixes |
||||||
|
# Version 2.2.2 bugfixes |
||||||
|
# Version 2.3 bugfixes and extended to support Trf |
||||||
|
# Version 2.4.x bugfixes |
||||||
|
|
||||||
|
# @mdgen EXCLUDE: base64c.tcl |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
namespace eval ::base64 { |
||||||
|
namespace export encode decode |
||||||
|
} |
||||||
|
|
||||||
|
package provide base64 2.6.1 |
||||||
|
|
||||||
|
if {[package vsatisfies [package require Tcl] 8.6 9]} { |
||||||
|
proc ::base64::encode {args} { |
||||||
|
binary encode base64 -maxlen 76 {*}$args |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base64::decode {string} { |
||||||
|
# Tcllib is strict with respect to end of input, yet lax for |
||||||
|
# invalid characters outside of that. |
||||||
|
regsub -all -- {[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/]} $string {} string |
||||||
|
binary decode base64 -strict $string |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if {![catch {package require Trf 2.0}]} { |
||||||
|
# Trf is available, so implement the functionality provided here |
||||||
|
# in terms of calls to Trf for speed. |
||||||
|
|
||||||
|
# ::base64::encode -- |
||||||
|
# |
||||||
|
# Base64 encode a given string. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# args ?-maxlen maxlen? ?-wrapchar wrapchar? string |
||||||
|
# |
||||||
|
# If maxlen is 0, the output is not wrapped. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A Base64 encoded version of $string, wrapped at $maxlen characters |
||||||
|
# by $wrapchar. |
||||||
|
|
||||||
|
proc ::base64::encode {args} { |
||||||
|
# Set the default wrapchar and maximum line length to match |
||||||
|
# the settings for MIME encoding (RFC 3548, RFC 2045). These |
||||||
|
# are the settings used by Trf as well. Various RFCs allow for |
||||||
|
# different wrapping characters and wraplengths, so these may |
||||||
|
# be overridden by command line options. |
||||||
|
set wrapchar "\n" |
||||||
|
set maxlen 76 |
||||||
|
|
||||||
|
if { [llength $args] == 0 } { |
||||||
|
error "wrong # args: should be \"[lindex [info level 0] 0]\ |
||||||
|
?-maxlen maxlen? ?-wrapchar wrapchar? string\"" |
||||||
|
} |
||||||
|
|
||||||
|
set optionStrings [list "-maxlen" "-wrapchar"] |
||||||
|
for {set i 0} {$i < [llength $args] - 1} {incr i} { |
||||||
|
set arg [lindex $args $i] |
||||||
|
set index [lsearch -glob $optionStrings "${arg}*"] |
||||||
|
if { $index == -1 } { |
||||||
|
error "unknown option \"$arg\": must be -maxlen or -wrapchar" |
||||||
|
} |
||||||
|
incr i |
||||||
|
if { $i >= [llength $args] - 1 } { |
||||||
|
error "value for \"$arg\" missing" |
||||||
|
} |
||||||
|
set val [lindex $args $i] |
||||||
|
|
||||||
|
# The name of the variable to assign the value to is extracted |
||||||
|
# from the list of known options, all of which have an |
||||||
|
# associated variable of the same name as the option without |
||||||
|
# a leading "-". The [string range] command is used to strip |
||||||
|
# of the leading "-" from the name of the option. |
||||||
|
# |
||||||
|
# FRINK: nocheck |
||||||
|
set [string range [lindex $optionStrings $index] 1 end] $val |
||||||
|
} |
||||||
|
|
||||||
|
# [string is] requires Tcl8.2; this works with 8.0 too |
||||||
|
if {[catch {expr {$maxlen % 2}}]} { |
||||||
|
return -code error "expected integer but got \"$maxlen\"" |
||||||
|
} elseif {$maxlen < 0} { |
||||||
|
return -code error "expected positive integer but got \"$maxlen\"" |
||||||
|
} |
||||||
|
|
||||||
|
set string [lindex $args end] |
||||||
|
set result [::base64 -mode encode -- $string] |
||||||
|
|
||||||
|
# Trf's encoder implicitly uses the settings -maxlen 76, |
||||||
|
# -wrapchar \n for its output. We may have to reflow this for |
||||||
|
# the settings chosen by the user. A second difference is that |
||||||
|
# Trf closes the output with the wrap char sequence, |
||||||
|
# always. The code here doesn't. Therefore 'trimright' is |
||||||
|
# needed in the fast cases. |
||||||
|
|
||||||
|
if {($maxlen == 76) && [string equal $wrapchar \n]} { |
||||||
|
# Both maxlen and wrapchar are identical to Trf's |
||||||
|
# settings. This is the super-fast case, because nearly |
||||||
|
# nothing has to be done. Only thing to do is strip a |
||||||
|
# terminating wrapchar. |
||||||
|
set result [string trimright $result] |
||||||
|
} elseif {$maxlen == 76} { |
||||||
|
# wrapchar has to be different here, length is the |
||||||
|
# same. We can use 'string map' to transform the wrap |
||||||
|
# information. |
||||||
|
set result [string map [list \n $wrapchar] \ |
||||||
|
[string trimright $result]] |
||||||
|
} elseif {$maxlen == 0} { |
||||||
|
# Have to reflow the output to no wrapping. Another fast |
||||||
|
# case using only 'string map'. 'trimright' is not needed |
||||||
|
# here. |
||||||
|
|
||||||
|
set result [string map [list \n ""] $result] |
||||||
|
} else { |
||||||
|
# Have to reflow the output from 76 to the chosen maxlen, |
||||||
|
# and possibly change the wrap sequence as well. |
||||||
|
|
||||||
|
# Note: After getting rid of the old wrap sequence we |
||||||
|
# extract the relevant segments from the string without |
||||||
|
# modifying the string. Modification, i.e. removal of the |
||||||
|
# processed part, means 'shifting down characters in |
||||||
|
# memory', making the algorithm O(n^2). By avoiding the |
||||||
|
# modification we stay in O(n). |
||||||
|
|
||||||
|
set result [string map [list \n ""] $result] |
||||||
|
set l [expr {[string length $result]-$maxlen}] |
||||||
|
for {set off 0} {$off < $l} {incr off $maxlen} { |
||||||
|
append res [string range $result $off [expr {$off+$maxlen-1}]] $wrapchar |
||||||
|
} |
||||||
|
append res [string range $result $off end] |
||||||
|
set result $res |
||||||
|
} |
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# ::base64::decode -- |
||||||
|
# |
||||||
|
# Base64 decode a given string. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# string The string to decode. Characters not in the base64 |
||||||
|
# alphabet are ignored (e.g., newlines) |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The decoded value. |
||||||
|
|
||||||
|
proc ::base64::decode {string} { |
||||||
|
regsub -all {\s} $string {} string |
||||||
|
::base64 -mode decode -- $string |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
# Without Trf use a pure tcl implementation |
||||||
|
|
||||||
|
namespace eval base64 { |
||||||
|
variable base64 {} |
||||||
|
variable base64_en {} |
||||||
|
|
||||||
|
# We create the auxiliary array base64_tmp, it will be unset later. |
||||||
|
variable base64_tmp |
||||||
|
variable i |
||||||
|
|
||||||
|
variable i 0 |
||||||
|
variable char |
||||||
|
foreach char {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \ |
||||||
|
a b c d e f g h i j k l m n o p q r s t u v w x y z \ |
||||||
|
0 1 2 3 4 5 6 7 8 9 + /} { |
||||||
|
set base64_tmp($char) $i |
||||||
|
lappend base64_en $char |
||||||
|
incr i |
||||||
|
} |
||||||
|
|
||||||
|
# |
||||||
|
# Create base64 as list: to code for instance C<->3, specify |
||||||
|
# that [lindex $base64 67] be 3 (C is 67 in ascii); non-coded |
||||||
|
# ascii chars get a {}. we later use the fact that lindex on a |
||||||
|
# non-existing index returns {}, and that [expr {} < 0] is true |
||||||
|
# |
||||||
|
|
||||||
|
# the last ascii char is 'z' |
||||||
|
variable char |
||||||
|
variable len |
||||||
|
variable val |
||||||
|
|
||||||
|
scan z %c len |
||||||
|
for {set i 0} {$i <= $len} {incr i} { |
||||||
|
set char [format %c $i] |
||||||
|
set val {} |
||||||
|
if {[info exists base64_tmp($char)]} { |
||||||
|
set val $base64_tmp($char) |
||||||
|
} else { |
||||||
|
set val {} |
||||||
|
} |
||||||
|
lappend base64 $val |
||||||
|
} |
||||||
|
|
||||||
|
# code the character "=" as -1; used to signal end of message |
||||||
|
scan = %c i |
||||||
|
set base64 [lreplace $base64 $i $i -1] |
||||||
|
|
||||||
|
# remove unneeded variables |
||||||
|
unset base64_tmp i char len val |
||||||
|
|
||||||
|
namespace export encode decode |
||||||
|
} |
||||||
|
|
||||||
|
# ::base64::encode -- |
||||||
|
# |
||||||
|
# Base64 encode a given string. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# args ?-maxlen maxlen? ?-wrapchar wrapchar? string |
||||||
|
# |
||||||
|
# If maxlen is 0, the output is not wrapped. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A Base64 encoded version of $string, wrapped at $maxlen characters |
||||||
|
# by $wrapchar. |
||||||
|
|
||||||
|
proc ::base64::encode {args} { |
||||||
|
set base64_en $::base64::base64_en |
||||||
|
|
||||||
|
# Set the default wrapchar and maximum line length to match |
||||||
|
# the settings for MIME encoding (RFC 3548, RFC 2045). These |
||||||
|
# are the settings used by Trf as well. Various RFCs allow for |
||||||
|
# different wrapping characters and wraplengths, so these may |
||||||
|
# be overridden by command line options. |
||||||
|
set wrapchar "\n" |
||||||
|
set maxlen 76 |
||||||
|
|
||||||
|
if { [llength $args] == 0 } { |
||||||
|
error "wrong # args: should be \"[lindex [info level 0] 0]\ |
||||||
|
?-maxlen maxlen? ?-wrapchar wrapchar? string\"" |
||||||
|
} |
||||||
|
|
||||||
|
set optionStrings [list "-maxlen" "-wrapchar"] |
||||||
|
for {set i 0} {$i < [llength $args] - 1} {incr i} { |
||||||
|
set arg [lindex $args $i] |
||||||
|
set index [lsearch -glob $optionStrings "${arg}*"] |
||||||
|
if { $index == -1 } { |
||||||
|
error "unknown option \"$arg\": must be -maxlen or -wrapchar" |
||||||
|
} |
||||||
|
incr i |
||||||
|
if { $i >= [llength $args] - 1 } { |
||||||
|
error "value for \"$arg\" missing" |
||||||
|
} |
||||||
|
set val [lindex $args $i] |
||||||
|
|
||||||
|
# The name of the variable to assign the value to is extracted |
||||||
|
# from the list of known options, all of which have an |
||||||
|
# associated variable of the same name as the option without |
||||||
|
# a leading "-". The [string range] command is used to strip |
||||||
|
# of the leading "-" from the name of the option. |
||||||
|
# |
||||||
|
# FRINK: nocheck |
||||||
|
set [string range [lindex $optionStrings $index] 1 end] $val |
||||||
|
} |
||||||
|
|
||||||
|
# [string is] requires Tcl8.2; this works with 8.0 too |
||||||
|
if {[catch {expr {$maxlen % 2}}]} { |
||||||
|
return -code error "expected integer but got \"$maxlen\"" |
||||||
|
} elseif {$maxlen < 0} { |
||||||
|
return -code error "expected positive integer but got \"$maxlen\"" |
||||||
|
} |
||||||
|
|
||||||
|
set string [lindex $args end] |
||||||
|
|
||||||
|
set result {} |
||||||
|
set state 0 |
||||||
|
set length 0 |
||||||
|
|
||||||
|
|
||||||
|
# Process the input bytes 3-by-3 |
||||||
|
|
||||||
|
binary scan $string c* X |
||||||
|
|
||||||
|
foreach {x y z} $X { |
||||||
|
ADD [lindex $base64_en [expr {($x >>2) & 0x3F}]] |
||||||
|
if {$y != {}} { |
||||||
|
ADD [lindex $base64_en [expr {(($x << 4) & 0x30) | (($y >> 4) & 0xF)}]] |
||||||
|
if {$z != {}} { |
||||||
|
ADD [lindex $base64_en [expr {(($y << 2) & 0x3C) | (($z >> 6) & 0x3)}]] |
||||||
|
ADD [lindex $base64_en [expr {($z & 0x3F)}]] |
||||||
|
} else { |
||||||
|
set state 2 |
||||||
|
break |
||||||
|
} |
||||||
|
} else { |
||||||
|
set state 1 |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if {$state == 1} { |
||||||
|
ADD [lindex $base64_en [expr {(($x << 4) & 0x30)}]] |
||||||
|
ADD = |
||||||
|
ADD = |
||||||
|
} elseif {$state == 2} { |
||||||
|
ADD [lindex $base64_en [expr {(($y << 2) & 0x3C)}]] |
||||||
|
ADD = |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
proc ::base64::ADD {x} { |
||||||
|
# The line length check is always done before appending so |
||||||
|
# that we don't get an extra newline if the output is a |
||||||
|
# multiple of $maxlen chars long. |
||||||
|
|
||||||
|
upvar 1 maxlen maxlen length length result result wrapchar wrapchar |
||||||
|
if {$maxlen && $length >= $maxlen} { |
||||||
|
append result $wrapchar |
||||||
|
set length 0 |
||||||
|
} |
||||||
|
append result $x |
||||||
|
incr length |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ::base64::decode -- |
||||||
|
# |
||||||
|
# Base64 decode a given string. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# string The string to decode. Characters not in the base64 |
||||||
|
# alphabet are ignored (e.g., newlines) |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The decoded value. |
||||||
|
|
||||||
|
proc ::base64::decode {string} { |
||||||
|
if {[string length $string] == 0} {return ""} |
||||||
|
|
||||||
|
set base64 $::base64::base64 |
||||||
|
set output "" ; # Fix for [Bug 821126] |
||||||
|
set nums {} |
||||||
|
|
||||||
|
binary scan $string c* X |
||||||
|
lappend X 61 ;# force a terminator |
||||||
|
foreach x $X { |
||||||
|
set bits [lindex $base64 $x] |
||||||
|
if {$bits >= 0} { |
||||||
|
if {[llength [lappend nums $bits]] == 4} { |
||||||
|
foreach {v w z y} $nums break |
||||||
|
set a [expr {($v << 2) | ($w >> 4)}] |
||||||
|
set b [expr {(($w & 0xF) << 4) | ($z >> 2)}] |
||||||
|
set c [expr {(($z & 0x3) << 6) | $y}] |
||||||
|
append output [binary format ccc $a $b $c] |
||||||
|
set nums {} |
||||||
|
} |
||||||
|
} elseif {$bits == -1} { |
||||||
|
# = indicates end of data. Output whatever chars are |
||||||
|
# left, if any. |
||||||
|
if {![llength $nums]} break |
||||||
|
# The encoding algorithm dictates that we can only |
||||||
|
# have 1 or 2 padding characters. If x=={}, we must |
||||||
|
# (*) have 12 bits of input (enough for 1 8-bit |
||||||
|
# output). If x!={}, we have 18 bits of input (enough |
||||||
|
# for 2 8-bit outputs). |
||||||
|
# |
||||||
|
# (*) If we don't then the input is broken (bug 2976290). |
||||||
|
|
||||||
|
foreach {v w z} $nums break |
||||||
|
|
||||||
|
# Bug 2976290 |
||||||
|
if {$w == {}} { |
||||||
|
return -code error "Not enough data to process padding" |
||||||
|
} |
||||||
|
|
||||||
|
set a [expr {($v << 2) | (($w & 0x30) >> 4)}] |
||||||
|
if {$z == {}} { |
||||||
|
append output [binary format c $a ] |
||||||
|
} else { |
||||||
|
set b [expr {(($w & 0xF) << 4) | (($z & 0x3C) >> 2)}] |
||||||
|
append output [binary format cc $a $b] |
||||||
|
} |
||||||
|
break |
||||||
|
} else { |
||||||
|
# RFC 2045 says that line breaks and other characters not part |
||||||
|
# of the Base64 alphabet must be ignored, and that the decoder |
||||||
|
# can optionally emit a warning or reject the message. We opt |
||||||
|
# not to do so, but to just ignore the character. |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
return $output |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
return |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
# base64c - Copyright (C) 2003 Pat Thoyts <patthoyts@users.sourceforge.net> |
||||||
|
# |
||||||
|
# This package is a place-holder for the critcl enhanced code present in |
||||||
|
# the tcllib base64 module. |
||||||
|
# |
||||||
|
# Normally this code will become part of the tcllibc library. |
||||||
|
# |
||||||
|
|
||||||
|
# @sak notprovided base64c |
||||||
|
package require critcl |
||||||
|
package provide base64c 0.1.1 |
||||||
|
|
||||||
|
namespace eval ::base64c { |
||||||
|
variable base64c_rcsid {$Id: base64c.tcl,v 1.5 2008/03/25 07:15:35 andreas_kupries Exp $} |
||||||
|
|
||||||
|
critcl::ccode { |
||||||
|
/* no code required in this file */ |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded base64 2.6.1 [list source [file join $dir base64.tcl]] |
||||||
|
package ifneeded uuencode 1.1.6 [list source [file join $dir uuencode.tcl]] |
||||||
|
package ifneeded yencode 1.1.4 [list source [file join $dir yencode.tcl]] |
||||||
|
package ifneeded ascii85 1.1.1 [list source [file join $dir ascii85.tcl]] |
@ -0,0 +1,337 @@ |
|||||||
|
# uuencode - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net> |
||||||
|
# |
||||||
|
# Provide a Tcl only implementation of uuencode and uudecode. |
||||||
|
# |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
package require Tcl 8.5 9; # tcl minimum version |
||||||
|
|
||||||
|
# Try and get some compiled helper package. |
||||||
|
if {[catch {package require tcllibc}]} { |
||||||
|
catch {package require Trf} |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval ::uuencode { |
||||||
|
namespace export encode decode uuencode uudecode |
||||||
|
} |
||||||
|
|
||||||
|
proc ::uuencode::Enc {c} { |
||||||
|
return [format %c [expr {($c != 0) ? (($c & 0x3f) + 0x20) : 0x60}]] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::uuencode::Encode {s} { |
||||||
|
set r {} |
||||||
|
binary scan $s c* d |
||||||
|
foreach {c1 c2 c3} $d { |
||||||
|
if {$c1 == {}} {set c1 0} |
||||||
|
if {$c2 == {}} {set c2 0} |
||||||
|
if {$c3 == {}} {set c3 0} |
||||||
|
append r [Enc [expr {$c1 >> 2}]] |
||||||
|
append r [Enc [expr {(($c1 << 4) & 0o060) | (($c2 >> 4) & 0o017)}]] |
||||||
|
append r [Enc [expr {(($c2 << 2) & 0o074) | (($c3 >> 6) & 0o003)}]] |
||||||
|
append r [Enc [expr {($c3 & 0o077)}]] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc ::uuencode::Decode {s} { |
||||||
|
if {[string length $s] == 0} {return ""} |
||||||
|
set r {} |
||||||
|
binary scan [pad $s] c* d |
||||||
|
|
||||||
|
foreach {c0 c1 c2 c3} $d { |
||||||
|
append r [format %c [expr {((($c0-0x20)&0x3F) << 2) & 0xFF |
||||||
|
| ((($c1-0x20)&0x3F) >> 4) & 0xFF}]] |
||||||
|
append r [format %c [expr {((($c1-0x20)&0x3F) << 4) & 0xFF |
||||||
|
| ((($c2-0x20)&0x3F) >> 2) & 0xFF}]] |
||||||
|
append r [format %c [expr {((($c2-0x20)&0x3F) << 6) & 0xFF |
||||||
|
| (($c3-0x20)&0x3F) & 0xFF}]] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# C coded version of the Encode/Decode functions for base64c package. |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
if {[package provide critcl] != {}} { |
||||||
|
namespace eval ::uuencode { |
||||||
|
critcl::ccode { |
||||||
|
#include <string.h> |
||||||
|
static unsigned char Enc(unsigned char c) { |
||||||
|
return (c != 0) ? ((c & 0x3f) + 0x20) : 0x60; |
||||||
|
} |
||||||
|
} |
||||||
|
critcl::ccommand CEncode {dummy interp objc objv} { |
||||||
|
Tcl_Obj *inputPtr, *resultPtr; |
||||||
|
Tcl_Size len, rlen, xtra; |
||||||
|
unsigned char *input, *p, *r; |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
inputPtr = objv[1]; |
||||||
|
input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ |
||||||
|
if (input == NULL) return TCL_ERROR; |
||||||
|
if ((xtra = (3 - (len % 3))) != 3) { |
||||||
|
if (Tcl_IsShared(inputPtr)) |
||||||
|
inputPtr = Tcl_DuplicateObj(inputPtr); |
||||||
|
input = Tcl_SetByteArrayLength(inputPtr, len + xtra); /* OK tcl9 */ |
||||||
|
memset(input + len, 0, xtra); |
||||||
|
len += xtra; |
||||||
|
} |
||||||
|
|
||||||
|
rlen = (len / 3) * 4; |
||||||
|
resultPtr = Tcl_NewObj(); |
||||||
|
r = Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ |
||||||
|
memset(r, 0, rlen); |
||||||
|
|
||||||
|
for (p = input; p < input + len; p += 3) { |
||||||
|
char a, b, c; |
||||||
|
a = *p; b = *(p+1), c = *(p+2); |
||||||
|
*r++ = Enc(a >> 2); |
||||||
|
*r++ = Enc(((a << 4) & 060) | ((b >> 4) & 017)); |
||||||
|
*r++ = Enc(((b << 2) & 074) | ((c >> 6) & 003)); |
||||||
|
*r++ = Enc(c & 077); |
||||||
|
} |
||||||
|
Tcl_SetObjResult(interp, resultPtr); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
|
||||||
|
critcl::ccommand CDecode {dummy interp objc objv} { |
||||||
|
Tcl_Obj *inputPtr, *resultPtr; |
||||||
|
Tcl_Size len, rlen, xtra; |
||||||
|
unsigned char *input, *p, *r; |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
/* if input is not mod 4, extend it with nuls */ |
||||||
|
inputPtr = objv[1]; |
||||||
|
input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ |
||||||
|
if (input == NULL) return TCL_ERROR; |
||||||
|
if ((xtra = (4 - (len % 4))) != 4) { |
||||||
|
if (Tcl_IsShared(inputPtr)) |
||||||
|
inputPtr = Tcl_DuplicateObj(inputPtr); |
||||||
|
input = Tcl_SetByteArrayLength(inputPtr, len + xtra); /* OK tcl9 */ |
||||||
|
memset(input + len, 0, xtra); |
||||||
|
len += xtra; |
||||||
|
} |
||||||
|
|
||||||
|
/* output will be 1/3 smaller than input and a multiple of 3 */ |
||||||
|
rlen = (len / 4) * 3; |
||||||
|
resultPtr = Tcl_NewObj(); |
||||||
|
r = Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ |
||||||
|
memset(r, 0, rlen); |
||||||
|
|
||||||
|
for (p = input; p < input + len; p += 4) { |
||||||
|
char a, b, c, d; |
||||||
|
a = *p; b = *(p+1), c = *(p+2), d = *(p+3); |
||||||
|
*r++ = (((a - 0x20) & 0x3f) << 2) | (((b - 0x20) & 0x3f) >> 4); |
||||||
|
*r++ = (((b - 0x20) & 0x3f) << 4) | (((c - 0x20) & 0x3f) >> 2); |
||||||
|
*r++ = (((c - 0x20) & 0x3f) << 6) | (((d - 0x20) & 0x3f) ); |
||||||
|
} |
||||||
|
Tcl_SetObjResult(interp, resultPtr); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# Description: |
||||||
|
# Permit more tolerant decoding of invalid input strings by padding to |
||||||
|
# a multiple of 4 bytes with nulls. |
||||||
|
# Result: |
||||||
|
# Returns the input string - possibly padded with uuencoded null chars. |
||||||
|
# |
||||||
|
proc ::uuencode::pad {s} { |
||||||
|
if {[set mod [expr {[string length $s] % 4}]] != 0} { |
||||||
|
append s [string repeat "`" [expr {4 - $mod}]] |
||||||
|
} |
||||||
|
return $s |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# If the Trf package is available then we shall use this by default but the |
||||||
|
# Tcllib implementations are always visible if needed (ie: for testing) |
||||||
|
if {[info commands ::uuencode::CDecode] != {}} { |
||||||
|
# tcllib critcl package |
||||||
|
interp alias {} ::uuencode::encode {} ::uuencode::CEncode |
||||||
|
interp alias {} ::uuencode::decode {} ::uuencode::CDecode |
||||||
|
} elseif {[package provide Trf] != {}} { |
||||||
|
proc ::uuencode::encode {s} { |
||||||
|
return [::uuencode -mode encode -- $s] |
||||||
|
} |
||||||
|
proc ::uuencode::decode {s} { |
||||||
|
return [::uuencode -mode decode -- [pad $s]] |
||||||
|
} |
||||||
|
} else { |
||||||
|
# pure-tcl then |
||||||
|
interp alias {} ::uuencode::encode {} ::uuencode::Encode |
||||||
|
interp alias {} ::uuencode::decode {} ::uuencode::Decode |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
proc ::uuencode::uuencode {args} { |
||||||
|
array set opts {mode 0o0644 filename {} name {}} |
||||||
|
set wrongargs "wrong \# args: should be\ |
||||||
|
\"uuencode ?-name string? ?-mode octal?\ |
||||||
|
(-file filename | ?--? string)\"" |
||||||
|
while {[string match -* [lindex $args 0]]} { |
||||||
|
switch -glob -- [lindex $args 0] { |
||||||
|
-f* { |
||||||
|
if {[llength $args] < 2} { |
||||||
|
return -code error $wrongargs |
||||||
|
} |
||||||
|
set opts(filename) [lindex $args 1] |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
} |
||||||
|
-m* { |
||||||
|
if {[llength $args] < 2} { |
||||||
|
return -code error $wrongargs |
||||||
|
} |
||||||
|
set opts(mode) [lindex $args 1] |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
} |
||||||
|
-n* { |
||||||
|
if {[llength $args] < 2} { |
||||||
|
return -code error $wrongargs |
||||||
|
} |
||||||
|
set opts(name) [lindex $args 1] |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
} |
||||||
|
-- { |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
break |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "bad option [lindex $args 0]:\ |
||||||
|
must be -file, -mode, or -name" |
||||||
|
} |
||||||
|
} |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(name) == {}} { |
||||||
|
set opts(name) $opts(filename) |
||||||
|
} |
||||||
|
if {$opts(name) == {}} { |
||||||
|
set opts(name) "data.dat" |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(filename) != {}} { |
||||||
|
set f [open $opts(filename) r] |
||||||
|
fconfigure $f -translation binary |
||||||
|
set data [read $f] |
||||||
|
close $f |
||||||
|
} else { |
||||||
|
if {[llength $args] != 1} { |
||||||
|
return -code error $wrongargs |
||||||
|
} |
||||||
|
set data [lindex $args 0] |
||||||
|
} |
||||||
|
|
||||||
|
set r {} |
||||||
|
append r [format "begin %o %s" $opts(mode) $opts(name)] "\n" |
||||||
|
for {set n 0} {$n < [string length $data]} {incr n 45} { |
||||||
|
set s [string range $data $n [expr {$n + 44}]] |
||||||
|
append r [Enc [string length $s]] |
||||||
|
append r [encode $s] "\n" |
||||||
|
} |
||||||
|
append r "`\nend" |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# Description: |
||||||
|
# Perform uudecoding of a file or data. A file may contain more than one |
||||||
|
# encoded data section so the result is a list where each element is a |
||||||
|
# three element list of the provided filename, the suggested mode and the |
||||||
|
# data itself. |
||||||
|
# |
||||||
|
proc ::uuencode::uudecode {args} { |
||||||
|
array set opts {mode 0o0644 filename {}} |
||||||
|
set wrongargs "wrong \# args: should be \"uudecode (-file filename | ?--? string)\"" |
||||||
|
while {[string match -* [lindex $args 0]]} { |
||||||
|
switch -glob -- [lindex $args 0] { |
||||||
|
-f* { |
||||||
|
if {[llength $args] < 2} { |
||||||
|
return -code error $wrongargs |
||||||
|
} |
||||||
|
set opts(filename) [lindex $args 1] |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
} |
||||||
|
-- { |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
break |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "bad option [lindex $args 0]:\ |
||||||
|
must be -file" |
||||||
|
} |
||||||
|
} |
||||||
|
set args [lreplace $args 0 0] |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(filename) != {}} { |
||||||
|
set f [open $opts(filename) r] |
||||||
|
set data [read $f] |
||||||
|
close $f |
||||||
|
} else { |
||||||
|
if {[llength $args] != 1} { |
||||||
|
return -code error $wrongargs |
||||||
|
} |
||||||
|
set data [lindex $args 0] |
||||||
|
} |
||||||
|
|
||||||
|
set state false |
||||||
|
set result {} |
||||||
|
|
||||||
|
foreach {line} [split $data "\n"] { |
||||||
|
switch -exact -- $state { |
||||||
|
false { |
||||||
|
if {[regexp {^begin ([0-7]+) ([^\s]*)} $line \ |
||||||
|
-> opts(mode) opts(name)]} { |
||||||
|
set state true |
||||||
|
set r {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
true { |
||||||
|
if {[string match "end" $line]} { |
||||||
|
set state false |
||||||
|
lappend result [list $opts(name) $opts(mode) $r] |
||||||
|
} else { |
||||||
|
scan $line %c c |
||||||
|
set n [expr {($c - 0x21)}] |
||||||
|
append r [string range \ |
||||||
|
[decode [string range $line 1 end]] 0 $n] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
package provide uuencode 1.1.6 |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# |
||||||
|
# Local variables: |
||||||
|
# mode: tcl |
||||||
|
# indent-tabs-mode: nil |
||||||
|
# End: |
||||||
|
|
@ -0,0 +1,309 @@ |
|||||||
|
# yencode.tcl - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net> |
||||||
|
# |
||||||
|
# Provide a Tcl only implementation of yEnc encoding algorithm |
||||||
|
# |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# FUTURE: Rework to allow switching between the tcl/critcl implementations. |
||||||
|
|
||||||
|
package require Tcl 8.5 9; # tcl minimum version |
||||||
|
catch {package require crc32}; # tcllib 1.1 |
||||||
|
catch {package require tcllibc}; # critcl enhancements for tcllib |
||||||
|
|
||||||
|
namespace eval ::yencode { |
||||||
|
namespace export encode decode yencode ydecode |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
proc ::yencode::Encode {s} { |
||||||
|
set r {} |
||||||
|
binary scan $s c* d |
||||||
|
foreach {c} $d { |
||||||
|
set v [expr {($c + 42) % 256}] |
||||||
|
if {$v == 0x00 || $v == 0x09 || $v == 0x0A |
||||||
|
|| $v == 0x0D || $v == 0x3D} { |
||||||
|
append r "=" |
||||||
|
set v [expr {($v + 64) % 256}] |
||||||
|
} |
||||||
|
append r [format %c $v] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
proc ::yencode::Decode {s} { |
||||||
|
if {[string length $s] == 0} {return ""} |
||||||
|
set r {} |
||||||
|
set esc 0 |
||||||
|
binary scan $s c* d |
||||||
|
foreach c $d { |
||||||
|
if {$c == 61 && $esc == 0} { |
||||||
|
set esc 1 |
||||||
|
continue |
||||||
|
} |
||||||
|
set v [expr {($c - 42) % 256}] |
||||||
|
if {$esc} { |
||||||
|
set v [expr {($v - 64) % 256}] |
||||||
|
set esc 0 |
||||||
|
} |
||||||
|
append r [format %c $v] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# C coded versions for critcl built base64c package |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
if {[package provide critcl] != {}} { |
||||||
|
namespace eval ::yencode { |
||||||
|
critcl::ccode { |
||||||
|
#include <string.h> |
||||||
|
} |
||||||
|
critcl::ccommand CEncode {dummy interp objc objv} { |
||||||
|
Tcl_Obj *inputPtr, *resultPtr; |
||||||
|
Tcl_Size len, rlen, xtra; |
||||||
|
unsigned char *input, *p, *r, v; |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
/* fetch the input data */ |
||||||
|
inputPtr = objv[1]; |
||||||
|
input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ |
||||||
|
if (input == NULL) return TCL_ERROR; |
||||||
|
|
||||||
|
/* calculate the length of the encoded result */ |
||||||
|
rlen = len; |
||||||
|
for (p = input; p < input + len; p++) { |
||||||
|
v = (*p + 42) % 256; |
||||||
|
if (v == 0 || v == 9 || v == 0x0A || v == 0x0D || v == 0x3D) |
||||||
|
rlen++; |
||||||
|
} |
||||||
|
|
||||||
|
/* allocate the output buffer */ |
||||||
|
resultPtr = Tcl_NewObj(); |
||||||
|
r = Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ |
||||||
|
|
||||||
|
/* encode the input */ |
||||||
|
for (p = input; p < input + len; p++) { |
||||||
|
v = (*p + 42) % 256; |
||||||
|
if (v == 0 || v == 9 || v == 0x0A || v == 0x0D || v == 0x3D) { |
||||||
|
*r++ = '='; |
||||||
|
v = (v + 64) % 256; |
||||||
|
} |
||||||
|
*r++ = v; |
||||||
|
} |
||||||
|
Tcl_SetObjResult(interp, resultPtr); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
|
||||||
|
critcl::ccommand CDecode {dummy interp objc objv} { |
||||||
|
Tcl_Obj *inputPtr, *resultPtr; |
||||||
|
Tcl_Size len, rlen, esc; |
||||||
|
unsigned char *input, *p, *r, v; |
||||||
|
|
||||||
|
if (objc != 2) { |
||||||
|
Tcl_WrongNumArgs(interp, 1, objv, "data"); /* OK tcl9 */ |
||||||
|
return TCL_ERROR; |
||||||
|
} |
||||||
|
|
||||||
|
/* fetch the input data */ |
||||||
|
inputPtr = objv[1]; |
||||||
|
input = Tcl_GetBytesFromObj(interp, inputPtr, &len); /* OK tcl9 */ |
||||||
|
if (input == NULL) return TCL_ERROR; |
||||||
|
|
||||||
|
/* allocate the output buffer */ |
||||||
|
resultPtr = Tcl_NewObj(); |
||||||
|
r = Tcl_SetByteArrayLength(resultPtr, len); /* OK tcl9 */ |
||||||
|
|
||||||
|
/* encode the input */ |
||||||
|
for (p = input, esc = 0, rlen = 0; p < input + len; p++) { |
||||||
|
if (*p == 61 && esc == 0) { |
||||||
|
esc = 1; |
||||||
|
continue; |
||||||
|
} |
||||||
|
v = (*p - 42) % 256; |
||||||
|
if (esc) { |
||||||
|
v = (v - 64) % 256; |
||||||
|
esc = 0; |
||||||
|
} |
||||||
|
*r++ = v; |
||||||
|
rlen++; |
||||||
|
} |
||||||
|
Tcl_SetByteArrayLength(resultPtr, rlen); /* OK tcl9 */ |
||||||
|
Tcl_SetObjResult(interp, resultPtr); |
||||||
|
return TCL_OK; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {[info commands ::yencode::CEncode] != {}} { |
||||||
|
interp alias {} ::yencode::encode {} ::yencode::CEncode |
||||||
|
interp alias {} ::yencode::decode {} ::yencode::CDecode |
||||||
|
} else { |
||||||
|
interp alias {} ::yencode::encode {} ::yencode::Encode |
||||||
|
interp alias {} ::yencode::decode {} ::yencode::Decode |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# Description: |
||||||
|
# Pop the nth element off a list. Used in options processing. |
||||||
|
# |
||||||
|
proc ::yencode::Pop {varname {nth 0}} { |
||||||
|
upvar $varname args |
||||||
|
set r [lindex $args $nth] |
||||||
|
set args [lreplace $args $nth $nth] |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
proc ::yencode::yencode {args} { |
||||||
|
array set opts {mode 0644 filename {} name {} line 128 crc32 1} |
||||||
|
while {[string match -* [lindex $args 0]]} { |
||||||
|
switch -glob -- [lindex $args 0] { |
||||||
|
-f* { set opts(filename) [Pop args 1] } |
||||||
|
-m* { set opts(mode) [Pop args 1] } |
||||||
|
-n* { set opts(name) [Pop args 1] } |
||||||
|
-l* { set opts(line) [Pop args 1] } |
||||||
|
-c* { set opts(crc32) [Pop args 1] } |
||||||
|
-- { Pop args ; break } |
||||||
|
default { |
||||||
|
set options [join [lsort [array names opts]] ", -"] |
||||||
|
return -code error "bad option [lindex $args 0]:\ |
||||||
|
must be -$options" |
||||||
|
} |
||||||
|
} |
||||||
|
Pop args |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(name) == {}} { |
||||||
|
set opts(name) $opts(filename) |
||||||
|
} |
||||||
|
if {$opts(name) == {}} { |
||||||
|
set opts(name) "data.dat" |
||||||
|
} |
||||||
|
if {! [string is boolean $opts(crc32)]} { |
||||||
|
return -code error "bad option -crc32: argument must be true or false" |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(filename) != {}} { |
||||||
|
set f [open $opts(filename) rb] |
||||||
|
fconfigure $f -translation binary |
||||||
|
set data [read $f] |
||||||
|
close $f |
||||||
|
} else { |
||||||
|
if {[llength $args] != 1} { |
||||||
|
return -code error "wrong \# args: should be\ |
||||||
|
\"yencode ?options? -file name | data\"" |
||||||
|
} |
||||||
|
set data [lindex $args 0] |
||||||
|
} |
||||||
|
|
||||||
|
set opts(size) [string length $data] |
||||||
|
|
||||||
|
set r {} |
||||||
|
append r [format "=ybegin line=%d size=%d name=%s" \ |
||||||
|
$opts(line) $opts(size) $opts(name)] "\n" |
||||||
|
|
||||||
|
set ndx 0 |
||||||
|
while {$ndx < $opts(size)} { |
||||||
|
set pln [string range $data $ndx [expr {$ndx + $opts(line) - 1}]] |
||||||
|
set enc [encode $pln] |
||||||
|
incr ndx [string length $pln] |
||||||
|
append r $enc "\r\n" |
||||||
|
} |
||||||
|
|
||||||
|
append r [format "=yend size=%d" $ndx] |
||||||
|
if {$opts(crc32)} { |
||||||
|
append r " crc32=" [crc::crc32 -format %x $data] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# Description: |
||||||
|
# Perform ydecoding of a file or data. A file may contain more than one |
||||||
|
# encoded data section so the result is a list where each element is a |
||||||
|
# three element list of the provided filename, the file size and the |
||||||
|
# data itself. |
||||||
|
# |
||||||
|
proc ::yencode::ydecode {args} { |
||||||
|
array set opts {mode 0644 filename {} name default.bin} |
||||||
|
while {[string match -* [lindex $args 0]]} { |
||||||
|
switch -glob -- [lindex $args 0] { |
||||||
|
-f* { set opts(filename) [Pop args 1] } |
||||||
|
-- { Pop args ; break; } |
||||||
|
default { |
||||||
|
set options [join [lsort [array names opts]] ", -"] |
||||||
|
return -code error "bad option [lindex $args 0]:\ |
||||||
|
must be -$opts" |
||||||
|
} |
||||||
|
} |
||||||
|
Pop args |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(filename) != {}} { |
||||||
|
set f [open $opts(filename) r] |
||||||
|
set data [read $f] |
||||||
|
close $f |
||||||
|
} else { |
||||||
|
if {[llength $args] != 1} { |
||||||
|
return -code error "wrong \# args: should be\ |
||||||
|
\"ydecode ?options? -file name | data\"" |
||||||
|
} |
||||||
|
set data [lindex $args 0] |
||||||
|
} |
||||||
|
|
||||||
|
set state false |
||||||
|
set result {} |
||||||
|
|
||||||
|
foreach {line} [split $data "\n"] { |
||||||
|
set line [string trimright $line "\r\n"] |
||||||
|
switch -exact -- $state { |
||||||
|
false { |
||||||
|
if {[string match "=ybegin*" $line]} { |
||||||
|
regexp {line=(\d+)} $line -> opts(line) |
||||||
|
regexp {size=(\d+)} $line -> opts(size) |
||||||
|
regexp {name=(\d+)} $line -> opts(name) |
||||||
|
|
||||||
|
if {$opts(name) == {}} { |
||||||
|
set opts(name) default.bin |
||||||
|
} |
||||||
|
|
||||||
|
set state true |
||||||
|
set r {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
true { |
||||||
|
if {[string match "=yend*" $line]} { |
||||||
|
set state false |
||||||
|
lappend result [list $opts(name) $opts(size) $r] |
||||||
|
} else { |
||||||
|
append r [decode $line] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
package provide yencode 1.1.4 |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# |
||||||
|
# Local variables: |
||||||
|
# mode: tcl |
||||||
|
# indent-tabs-mode: nil |
||||||
|
# End: |
||||||
|
|
@ -0,0 +1,999 @@ |
|||||||
|
# bee.tcl -- |
||||||
|
# |
||||||
|
# BitTorrent Bee de- and encoder. |
||||||
|
# |
||||||
|
# Copyright (c) 2004 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
||||||
|
# See the file license.terms. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval ::bee { |
||||||
|
# Encoder commands |
||||||
|
namespace export \ |
||||||
|
encodeString encodeNumber \ |
||||||
|
encodeListArgs encodeList \ |
||||||
|
encodeDictArgs encodeDict |
||||||
|
|
||||||
|
# Decoder commands. |
||||||
|
namespace export \ |
||||||
|
decode \ |
||||||
|
decodeChannel \ |
||||||
|
decodeCancel \ |
||||||
|
decodePush |
||||||
|
|
||||||
|
# Channel decoders, reference to state information, keyed by |
||||||
|
# channel handle. |
||||||
|
|
||||||
|
variable bee |
||||||
|
array set bee {} |
||||||
|
|
||||||
|
# Counter for generation of names for the state variables. |
||||||
|
|
||||||
|
variable count 0 |
||||||
|
|
||||||
|
# State information for the channel decoders. |
||||||
|
|
||||||
|
# stateN, with N an integer number counting from 0 on up. |
||||||
|
# ...(chan) Handle of channel the decoder is for. |
||||||
|
# ...(cmd) Command prefix, completion callback |
||||||
|
# ...(exact) Boolean flag, set for exact processing. |
||||||
|
# ...(read) Buffer for new characters to process. |
||||||
|
# ...(type) Type of current value (integer, string, list, dict) |
||||||
|
# ...(value) Buffer for assembling the current value. |
||||||
|
# ...(pend) Stack of pending 'value' buffers, for nested |
||||||
|
# containers. |
||||||
|
# ...(state) Current state of the decoding state machine. |
||||||
|
|
||||||
|
# States of the finite automaton ... |
||||||
|
# intro - One char, type of value, or 'e' as stop of container. |
||||||
|
# signum - sign or digit, for integer. |
||||||
|
# idigit - digit, for integer, or 'e' as stop |
||||||
|
# ldigit - digit, for length of string, or : |
||||||
|
# data - string data, 'get' characters. |
||||||
|
# Containers via 'pend'. |
||||||
|
|
||||||
|
#Debugging help, nesting level |
||||||
|
#variable X 0 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::encodeString -- |
||||||
|
# |
||||||
|
# Encode a string to bee-format. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# string The string to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The bee-encoded form of the string. |
||||||
|
|
||||||
|
proc ::bee::encodeString {string} { |
||||||
|
return "[string length $string]:$string" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::encodeNumber -- |
||||||
|
# |
||||||
|
# Encode an integer number to bee-format. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# num The integer number to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The bee-encoded form of the integer number. |
||||||
|
|
||||||
|
proc ::bee::encodeNumber {num} { |
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $num]} { |
||||||
|
return -code error "Expected integer number, got \"$num\"" |
||||||
|
} |
||||||
|
|
||||||
|
# The reformatting deals with hex, octal and other tcl |
||||||
|
# representation of the value. In other words we normalize the |
||||||
|
# string representation of the input value. |
||||||
|
|
||||||
|
set num [format %d $num] |
||||||
|
return "i${num}e" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::encodeList -- |
||||||
|
# |
||||||
|
# Encode a list of bee-coded values to bee-format. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# list The list to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The bee-encoded form of the list. |
||||||
|
|
||||||
|
proc ::bee::encodeList {list} { |
||||||
|
return "l[join $list ""]e" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::encodeListArgs -- |
||||||
|
# |
||||||
|
# Encode a variable list of bee-coded values to bee-format. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# args The values to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The bee-encoded form of the list of values. |
||||||
|
|
||||||
|
proc ::bee::encodeListArgs {args} { |
||||||
|
return [encodeList $args] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::encodeDict -- |
||||||
|
# |
||||||
|
# Encode a dictionary of keys and bee-coded values to bee-format. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# dict The dictionary to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The bee-encoded form of the dictionary. |
||||||
|
|
||||||
|
proc ::bee::encodeDict {dict} { |
||||||
|
if {([llength $dict] % 2) == 1} { |
||||||
|
return -code error "Expected even number of elements, got \"[llength $dict]\"" |
||||||
|
} |
||||||
|
set temp [list] |
||||||
|
foreach {k v} $dict { |
||||||
|
lappend temp [list $k $v] |
||||||
|
} |
||||||
|
set res "d" |
||||||
|
foreach item [lsort -index 0 $temp] { |
||||||
|
foreach {k v} $item break |
||||||
|
append res [encodeString $k]$v |
||||||
|
} |
||||||
|
append res "e" |
||||||
|
return $res |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::encodeDictArgs -- |
||||||
|
# |
||||||
|
# Encode a variable dictionary of keys and bee-coded values to bee-format. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# args The keys and values to encode. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The bee-encoded form of the dictionary. |
||||||
|
|
||||||
|
proc ::bee::encodeDictArgs {args} { |
||||||
|
return [encodeDict $args] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::decode -- |
||||||
|
# |
||||||
|
# Decode a bee-encoded value and returns the embedded tcl |
||||||
|
# value. For containers this recurses into the contained value. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# value The string containing the bee-encoded value to decode. |
||||||
|
# evar Optional. If set the name of the variable to store the |
||||||
|
# index of the first character after the decoded value to. |
||||||
|
# start Optional. If set the index of the first character of the |
||||||
|
# value to decode. Defaults to 0, i.e. the beginning of the |
||||||
|
# string. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The tcl value embedded in the encoded string. |
||||||
|
|
||||||
|
proc ::bee::decode {value {evar {}} {start 0}} { |
||||||
|
#variable X |
||||||
|
#puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout |
||||||
|
|
||||||
|
if {$evar ne ""} {upvar 1 $evar end} else {set end _} |
||||||
|
|
||||||
|
if {[string length $value] < ($start+2)} { |
||||||
|
# This checked that the 'start' index is still in the string, |
||||||
|
# and the end of the value most likely as well. Note that each |
||||||
|
# encoded value consists of at least two characters (the |
||||||
|
# bracketing characters for integer, list, and dict, and for |
||||||
|
# string at least one digit length and the colon). |
||||||
|
|
||||||
|
#puts \t[string length $value]\ <\ ($start+2) |
||||||
|
return -code error "String not large enough for value" |
||||||
|
} |
||||||
|
|
||||||
|
set type [string index $value $start] |
||||||
|
|
||||||
|
#puts -nonewline " $type=" ; flush stdout |
||||||
|
|
||||||
|
if {$type eq "i"} { |
||||||
|
# Extract integer |
||||||
|
#puts -nonewline integer ; flush stdout |
||||||
|
|
||||||
|
incr start ; # Skip over intro 'i'. |
||||||
|
set end [string first e $value $start] |
||||||
|
if {$end < 0} { |
||||||
|
return -code error "End of integer number not found" |
||||||
|
} |
||||||
|
incr end -1 ; # Get last character before closing 'e'. |
||||||
|
set num [string range $value $start $end] |
||||||
|
##nagelfar ignore |
||||||
|
if { |
||||||
|
[regexp {^-0+$} $num] || |
||||||
|
![string is integer -strict $num] || |
||||||
|
(([string length $num] > 1) && [string match 0* $num]) |
||||||
|
} { |
||||||
|
return -code error "Expected integer number, got \"$num\"" |
||||||
|
} |
||||||
|
incr end 2 ; # Step after closing 'e' to the beginning of |
||||||
|
# ........ ; # the next bee-value behind the current one. |
||||||
|
|
||||||
|
#puts " ($num) @$end" |
||||||
|
return [format %d $num] |
||||||
|
|
||||||
|
} elseif {($type eq "l") || ($type eq "d")} { |
||||||
|
#puts -nonewline $type\n ; flush stdout |
||||||
|
|
||||||
|
# Extract list or dictionary, recursively each contained |
||||||
|
# element. From the perspective of the decoder this is the |
||||||
|
# same, the tcl representation of both is a list, and for a |
||||||
|
# dictionary keys and values are also already in the correct |
||||||
|
# order. |
||||||
|
|
||||||
|
set result [list] |
||||||
|
incr start ; # Step over intro 'e' to beginning of the first |
||||||
|
# ........ ; # contained value, or behind the container (if |
||||||
|
# ........ ; # empty). |
||||||
|
|
||||||
|
set end $start |
||||||
|
#incr X |
||||||
|
while {[string index $value $start] ne "e"} { |
||||||
|
lappend result [decode $value end $start] |
||||||
|
set start $end |
||||||
|
} |
||||||
|
#incr X -1 |
||||||
|
incr end |
||||||
|
|
||||||
|
#puts "[string repeat " " $X]($result) @$end" |
||||||
|
|
||||||
|
if {$type eq "d" && ([llength $result] % 2 == 1)} { |
||||||
|
return -code error "Dictionary has to be of even length" |
||||||
|
} |
||||||
|
return $result |
||||||
|
|
||||||
|
} elseif {[string match {[0-9]} $type]} { |
||||||
|
#puts -nonewline string ; flush stdout |
||||||
|
|
||||||
|
# Extract string. First the length, bounded by a colon, then |
||||||
|
# the appropriate number of characters. |
||||||
|
|
||||||
|
set end [string first : $value $start] |
||||||
|
if {$end < 0} { |
||||||
|
return -code error "End of string length not found" |
||||||
|
} |
||||||
|
incr end -1 |
||||||
|
set length [string range $value $start $end] |
||||||
|
incr end 2 ;# Skip to beginning of the string after the colon |
||||||
|
|
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $length]} { |
||||||
|
return -code error "Expected integer number for string length, got \"$length\"" |
||||||
|
} elseif {$length < 0} { |
||||||
|
# This cannot happen. To happen "-" has to be first character, |
||||||
|
# and this is caught as unknown bee-type. |
||||||
|
return -code error "Illegal negative string length" |
||||||
|
} elseif {($end + $length) > [string length $value]} { |
||||||
|
return -code error "String not large enough for value" |
||||||
|
} |
||||||
|
|
||||||
|
#puts -nonewline \[$length\] ; flush stdout |
||||||
|
set length [format %d $length] |
||||||
|
if {$length > 0} { |
||||||
|
set start $end |
||||||
|
incr end $length |
||||||
|
incr end -1 |
||||||
|
set result [string range $value $start $end] |
||||||
|
incr end |
||||||
|
} else { |
||||||
|
set result "" |
||||||
|
} |
||||||
|
|
||||||
|
#puts " ($result) @$end" |
||||||
|
return $result |
||||||
|
|
||||||
|
} else { |
||||||
|
return -code error "Unknown bee-type \"$type\"" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::decodeIndices -- |
||||||
|
# |
||||||
|
# Similar to 'decode', but does not return the decoded tcl values, |
||||||
|
# but a structure containing the start- and end-indices for all |
||||||
|
# values in the structure. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# value The string containing the bee-encoded value to decode. |
||||||
|
# evar Optional. If set the name of the variable to store the |
||||||
|
# index of the first character after the decoded value to. |
||||||
|
# start Optional. If set the index of the first character of the |
||||||
|
# value to decode. Defaults to 0, i.e. the beginning of the |
||||||
|
# string. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The structure of the value, with indices and types for all |
||||||
|
# contained elements. |
||||||
|
|
||||||
|
proc ::bee::decodeIndices {value {evar {}} {start 0}} { |
||||||
|
#variable X |
||||||
|
#puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout |
||||||
|
|
||||||
|
if {$evar ne ""} {upvar 1 $evar end} else {set end _} |
||||||
|
|
||||||
|
if {[string length $value] < ($start+2)} { |
||||||
|
# This checked that the 'start' index is still in the string, |
||||||
|
# and the end of the value most likely as well. Note that each |
||||||
|
# encoded value consists of at least two characters (the |
||||||
|
# bracketing characters for integer, list, and dict, and for |
||||||
|
# string at least one digit length and the colon). |
||||||
|
|
||||||
|
#puts \t[string length $value]\ <\ ($start+2) |
||||||
|
return -code error "String not large enough for value" |
||||||
|
} |
||||||
|
|
||||||
|
set type [string index $value $start] |
||||||
|
|
||||||
|
#puts -nonewline " $type=" ; flush stdout |
||||||
|
|
||||||
|
if {$type eq "i"} { |
||||||
|
# Extract integer |
||||||
|
#puts -nonewline integer ; flush stdout |
||||||
|
|
||||||
|
set begin $start |
||||||
|
|
||||||
|
incr start ; # Skip over intro 'i'. |
||||||
|
set end [string first e $value $start] |
||||||
|
if {$end < 0} { |
||||||
|
return -code error "End of integer number not found" |
||||||
|
} |
||||||
|
incr end -1 ; # Get last character before closing 'e'. |
||||||
|
set num [string range $value $start $end] |
||||||
|
##nagelfar ignore |
||||||
|
if { |
||||||
|
[regexp {^-0+$} $num] || |
||||||
|
![string is integer -strict $num] || |
||||||
|
(([string length $num] > 1) && [string match 0* $num]) |
||||||
|
} { |
||||||
|
return -code error "Expected integer number, got \"$num\"" |
||||||
|
} |
||||||
|
incr end |
||||||
|
set stop $end |
||||||
|
incr end 1 ; # Step after closing 'e' to the beginning of |
||||||
|
# ........ ; # the next bee-value behind the current one. |
||||||
|
|
||||||
|
#puts " ($num) @$end" |
||||||
|
return [list integer $begin $stop] |
||||||
|
|
||||||
|
} elseif {$type eq "l"} { |
||||||
|
#puts -nonewline $type\n ; flush stdout |
||||||
|
|
||||||
|
# Extract list, recursively each contained element. |
||||||
|
|
||||||
|
set result [list] |
||||||
|
|
||||||
|
lappend result list $start @ |
||||||
|
|
||||||
|
incr start ; # Step over intro 'e' to beginning of the first |
||||||
|
# ........ ; # contained value, or behind the container (if |
||||||
|
# ........ ; # empty). |
||||||
|
|
||||||
|
set end $start |
||||||
|
#incr X |
||||||
|
|
||||||
|
set contained [list] |
||||||
|
while {[string index $value $start] ne "e"} { |
||||||
|
lappend contained [decodeIndices $value end $start] |
||||||
|
set start $end |
||||||
|
} |
||||||
|
lappend result $contained |
||||||
|
#incr X -1 |
||||||
|
set stop $end |
||||||
|
incr end |
||||||
|
|
||||||
|
#puts "[string repeat " " $X]($result) @$end" |
||||||
|
|
||||||
|
return [lreplace $result 2 2 $stop] |
||||||
|
|
||||||
|
} elseif {($type eq "l") || ($type eq "d")} { |
||||||
|
#puts -nonewline $type\n ; flush stdout |
||||||
|
|
||||||
|
# Extract dictionary, recursively each contained element. |
||||||
|
|
||||||
|
set result [list] |
||||||
|
|
||||||
|
lappend result dict $start @ |
||||||
|
|
||||||
|
incr start ; # Step over intro 'e' to beginning of the first |
||||||
|
# ........ ; # contained value, or behind the container (if |
||||||
|
# ........ ; # empty). |
||||||
|
|
||||||
|
set end $start |
||||||
|
set atkey 1 |
||||||
|
#incr X |
||||||
|
|
||||||
|
set contained [list] |
||||||
|
set val [list] |
||||||
|
while {[string index $value $start] ne "e"} { |
||||||
|
if {$atkey} { |
||||||
|
lappend contained [decode $value {} $start] |
||||||
|
lappend val [decodeIndices $value end $start] |
||||||
|
set atkey 0 |
||||||
|
} else { |
||||||
|
lappend val [decodeIndices $value end $start] |
||||||
|
lappend contained $val |
||||||
|
set val [list] |
||||||
|
set atkey 1 |
||||||
|
} |
||||||
|
set start $end |
||||||
|
} |
||||||
|
lappend result $contained |
||||||
|
#incr X -1 |
||||||
|
set stop $end |
||||||
|
incr end |
||||||
|
|
||||||
|
#puts "[string repeat " " $X]($result) @$end" |
||||||
|
|
||||||
|
if {[llength $result] % 2 == 1} { |
||||||
|
return -code error "Dictionary has to be of even length" |
||||||
|
} |
||||||
|
return [lreplace $result 2 2 $stop] |
||||||
|
|
||||||
|
} elseif {[string match {[0-9]} $type]} { |
||||||
|
#puts -nonewline string ; flush stdout |
||||||
|
|
||||||
|
# Extract string. First the length, bounded by a colon, then |
||||||
|
# the appropriate number of characters. |
||||||
|
|
||||||
|
set end [string first : $value $start] |
||||||
|
if {$end < 0} { |
||||||
|
return -code error "End of string length not found" |
||||||
|
} |
||||||
|
incr end -1 |
||||||
|
set length [string range $value $start $end] |
||||||
|
incr end 2 ;# Skip to beginning of the string after the colon |
||||||
|
|
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $length]} { |
||||||
|
return -code error "Expected integer number for string length, got \"$length\"" |
||||||
|
} elseif {$length < 0} { |
||||||
|
# This cannot happen. To happen "-" has to be first character, |
||||||
|
# and this is caught as unknown bee-type. |
||||||
|
return -code error "Illegal negative string length" |
||||||
|
} elseif {($end + $length) > [string length $value]} { |
||||||
|
return -code error "String not large enough for value" |
||||||
|
} |
||||||
|
|
||||||
|
set length [format %d $length] |
||||||
|
#puts -nonewline \[$length\] ; flush stdout |
||||||
|
incr end -1 |
||||||
|
if {$length > 0} { |
||||||
|
incr end $length |
||||||
|
set stop $end |
||||||
|
} else { |
||||||
|
set stop $end |
||||||
|
} |
||||||
|
incr end |
||||||
|
|
||||||
|
#puts " ($result) @$end" |
||||||
|
return [list string $start $stop] |
||||||
|
|
||||||
|
} else { |
||||||
|
return -code error "Unknown bee-type \"$type\"" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ::bee::decodeChannel -- |
||||||
|
# |
||||||
|
# Attach decoder for a bee-value to a channel. See the |
||||||
|
# documentation for details. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# chan Channel to attach to. |
||||||
|
# -command cmdprefix Completion callback. Required. |
||||||
|
# -exact Keep running after completion. |
||||||
|
# -prefix data Seed for decode buffer. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A token to use when referring to the decoder. |
||||||
|
# For example when canceling it. |
||||||
|
|
||||||
|
proc ::bee::decodeChannel {chan args} { |
||||||
|
variable bee |
||||||
|
if {[info exists bee($chan)]} { |
||||||
|
return -code error "bee-Decoder already active for channel" |
||||||
|
} |
||||||
|
|
||||||
|
# Create state and token. |
||||||
|
|
||||||
|
variable count |
||||||
|
variable [set st state$count] |
||||||
|
array set $st {} |
||||||
|
set bee($chan) $st |
||||||
|
upvar 0 $st state |
||||||
|
incr count |
||||||
|
|
||||||
|
# Initialize the decoder state, process the options. When |
||||||
|
# encountering errors here destroy the half-baked state before |
||||||
|
# throwing the message. |
||||||
|
|
||||||
|
set state(chan) $chan |
||||||
|
array set state { |
||||||
|
exact 0 |
||||||
|
type ? |
||||||
|
read {} |
||||||
|
value {} |
||||||
|
pend {} |
||||||
|
state intro |
||||||
|
get 1 |
||||||
|
} |
||||||
|
|
||||||
|
while {[llength $args]} { |
||||||
|
set option [lindex $args 0] |
||||||
|
set args [lrange $args 1 end] |
||||||
|
if {$option eq "-command"} { |
||||||
|
if {![llength $args]} { |
||||||
|
unset bee($chan) |
||||||
|
unset state |
||||||
|
return -code error "Missing value for option -command." |
||||||
|
} |
||||||
|
set state(cmd) [lindex $args 0] |
||||||
|
set args [lrange $args 1 end] |
||||||
|
|
||||||
|
} elseif {$option eq "-prefix"} { |
||||||
|
if {![llength $args]} { |
||||||
|
unset bee($chan) |
||||||
|
unset state |
||||||
|
return -code error "Missing value for option -prefix." |
||||||
|
} |
||||||
|
set state(read) [lindex $args 0] |
||||||
|
set args [lrange $args 1 end] |
||||||
|
|
||||||
|
} elseif {$option eq "-exact"} { |
||||||
|
set state(exact) 1 |
||||||
|
} else { |
||||||
|
unset bee($chan) |
||||||
|
unset state |
||||||
|
return -code error "Illegal option \"$option\",\ |
||||||
|
expected \"-command\", \"-prefix\", or \"-keep\"" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {![info exists state(cmd)]} { |
||||||
|
unset bee($chan) |
||||||
|
unset state |
||||||
|
return -code error "Missing required completion callback." |
||||||
|
} |
||||||
|
|
||||||
|
# Set up the processing of incoming data. |
||||||
|
|
||||||
|
fileevent $chan readable [list ::bee::Process $chan $bee($chan)] |
||||||
|
|
||||||
|
# Return the name of the state array as token. |
||||||
|
return $bee($chan) |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::Parse -- |
||||||
|
# |
||||||
|
# Internal helper. Fileevent handler for a decoder. |
||||||
|
# Parses input and handles both error and eof conditions. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# token The decoder to run on its input. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
|
||||||
|
proc ::bee::Process {chan token} { |
||||||
|
if {[catch {Parse $token} msg]} { |
||||||
|
# Something failed. Destroy and report. |
||||||
|
Command $token error $msg |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if {[eof $chan]} { |
||||||
|
# Having data waiting, either in the input queue, or in the |
||||||
|
# output stack (of nested containers) is a failure. Report |
||||||
|
# this instead of the eof. |
||||||
|
|
||||||
|
variable $token |
||||||
|
upvar 0 $token state |
||||||
|
|
||||||
|
if { |
||||||
|
[string length $state(read)] || |
||||||
|
[llength $state(pend)] || |
||||||
|
[string length $state(value)] || |
||||||
|
($state(state) ne "intro") |
||||||
|
} { |
||||||
|
Command $token error "Incomplete value at end of channel" |
||||||
|
} else { |
||||||
|
Command $token eof |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::Parse -- |
||||||
|
# |
||||||
|
# Internal helper. Reading from the channel and parsing the input. |
||||||
|
# Uses a hardwired state machine. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# token The decoder to run on its input. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
|
||||||
|
proc ::bee::Parse {token} { |
||||||
|
variable $token |
||||||
|
upvar 0 $token state |
||||||
|
upvar 0 state(state) current |
||||||
|
upvar 0 state(read) input |
||||||
|
upvar 0 state(type) type |
||||||
|
upvar 0 state(value) value |
||||||
|
upvar 0 state(pend) pend |
||||||
|
upvar 0 state(exact) exact |
||||||
|
upvar 0 state(get) get |
||||||
|
set chan $state(chan) |
||||||
|
|
||||||
|
#puts Parse/$current |
||||||
|
|
||||||
|
if {!$exact} { |
||||||
|
# Add all waiting characters to the buffer so that we can process as |
||||||
|
# much as is possible in one go. |
||||||
|
append input [read $chan] |
||||||
|
} else { |
||||||
|
# Exact reading. Usually one character, but when in the data |
||||||
|
# section for a string value we know for how many characters |
||||||
|
# we are looking for. |
||||||
|
|
||||||
|
append input [read $chan $get] |
||||||
|
} |
||||||
|
|
||||||
|
# We got nothing, do nothing. |
||||||
|
if {![string length $input]} return |
||||||
|
|
||||||
|
|
||||||
|
if {$current eq "data"} { |
||||||
|
# String data, this can be done faster, as we read longer |
||||||
|
# sequences of characters for this. |
||||||
|
set l [string length $input] |
||||||
|
if {$l < $get} { |
||||||
|
# Not enough, wait for more. |
||||||
|
append value $input |
||||||
|
incr get -$l |
||||||
|
return |
||||||
|
} elseif {$l == $get} { |
||||||
|
# Got all, exactly. Prepare state machine for next value. |
||||||
|
|
||||||
|
if {[Complete $token $value$input]} return |
||||||
|
|
||||||
|
set current intro |
||||||
|
set get 1 |
||||||
|
set value "" |
||||||
|
set input "" |
||||||
|
|
||||||
|
return |
||||||
|
} else { |
||||||
|
# Got more than required (only for !exact). |
||||||
|
|
||||||
|
incr get -1 |
||||||
|
if {[Complete $token $value[string range $input 0 $get]]} {return} |
||||||
|
|
||||||
|
incr get |
||||||
|
set input [string range $input $get end] |
||||||
|
set get 1 |
||||||
|
set value "" |
||||||
|
set current intro |
||||||
|
# This now falls into the loop below. |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set where 0 |
||||||
|
set n [string length $input] |
||||||
|
|
||||||
|
#puts Parse/$n |
||||||
|
|
||||||
|
while {$where < $n} { |
||||||
|
# Hardwired state machine. Get current character. |
||||||
|
set ch [string index $input $where] |
||||||
|
|
||||||
|
#puts Parse/@$where/$current/$ch/ |
||||||
|
if {$current eq "intro"} { |
||||||
|
# First character of a value. |
||||||
|
|
||||||
|
if {$ch eq "i"} { |
||||||
|
# Begin reading integer. |
||||||
|
set type integer |
||||||
|
set current signum |
||||||
|
} elseif {$ch eq "l"} { |
||||||
|
# Begin a list. |
||||||
|
set type list |
||||||
|
lappend pend list {} |
||||||
|
#set current intro |
||||||
|
|
||||||
|
} elseif {$ch eq "d"} { |
||||||
|
# Begin a dictionary. |
||||||
|
set type dict |
||||||
|
lappend pend dict {} |
||||||
|
#set current intro |
||||||
|
|
||||||
|
} elseif {$ch eq "e"} { |
||||||
|
# Close a container. Throw an error if there is no |
||||||
|
# container to close. |
||||||
|
|
||||||
|
if {![llength $pend]} { |
||||||
|
return -code error "End of container outside of container." |
||||||
|
} |
||||||
|
|
||||||
|
set v [lindex $pend end] |
||||||
|
set t [lindex $pend end-1] |
||||||
|
set pend [lrange $pend 0 end-2] |
||||||
|
|
||||||
|
if {$t eq "dict" && ([llength $v] % 2 == 1)} { |
||||||
|
return -code error "Dictionary has to be of even length" |
||||||
|
} |
||||||
|
|
||||||
|
if {[Complete $token $v]} {return} |
||||||
|
set current intro |
||||||
|
|
||||||
|
} elseif {[string match {[0-9]} $ch]} { |
||||||
|
# Begin reading a string, length section first. |
||||||
|
set type string |
||||||
|
set current ldigit |
||||||
|
set value $ch |
||||||
|
|
||||||
|
} else { |
||||||
|
# Unknown type. Throw error. |
||||||
|
return -code error "Unknown bee-type \"$ch\"" |
||||||
|
} |
||||||
|
|
||||||
|
# To next character. |
||||||
|
incr where |
||||||
|
} elseif {$current eq "signum"} { |
||||||
|
# Integer number, a minus sign, or a digit. |
||||||
|
if {[string match {[-0-9]} $ch]} { |
||||||
|
append value $ch |
||||||
|
set current idigit |
||||||
|
} else { |
||||||
|
return -code error "Syntax error in integer,\ |
||||||
|
expected sign or digit, got \"$ch\"" |
||||||
|
} |
||||||
|
incr where |
||||||
|
|
||||||
|
} elseif {$current eq "idigit"} { |
||||||
|
# Integer number, digit or closing 'e'. |
||||||
|
|
||||||
|
if {[string match {[-0-9]} $ch]} { |
||||||
|
append value $ch |
||||||
|
} elseif {$ch eq "e"} { |
||||||
|
# Integer closes. Validate and report. |
||||||
|
#puts validate |
||||||
|
##nagelfar ignore |
||||||
|
if { |
||||||
|
[regexp {^-0+$} $value] || |
||||||
|
![string is integer -strict $value] || |
||||||
|
(([string length $value] > 1) && [string match 0* $value]) |
||||||
|
} { |
||||||
|
return -code error "Expected integer number, got \"$value\"" |
||||||
|
} |
||||||
|
set value [format %d $value] |
||||||
|
if {[Complete $token $value]} {return} |
||||||
|
set value "" |
||||||
|
set current intro |
||||||
|
} else { |
||||||
|
return -code error "Syntax error in integer,\ |
||||||
|
expected digit, or 'e', got \"$ch\"" |
||||||
|
} |
||||||
|
incr where |
||||||
|
|
||||||
|
} elseif {$current eq "ldigit"} { |
||||||
|
# String, length section, digit, or : |
||||||
|
|
||||||
|
if {[string match {[-0-9]} $ch]} { |
||||||
|
append value $ch |
||||||
|
|
||||||
|
} elseif {$ch eq ":"} { |
||||||
|
# Length section closes, validate, |
||||||
|
# then perform data processing. |
||||||
|
|
||||||
|
set num $value |
||||||
|
##nagelfar ignore |
||||||
|
if { |
||||||
|
[regexp {^-0+$} $num] || |
||||||
|
![string is integer -strict $num] || |
||||||
|
(([string length $num] > 1) && [string match 0* $num]) |
||||||
|
} { |
||||||
|
return -code error "Expected integer number as string length, got \"$num\"" |
||||||
|
} |
||||||
|
set num [format %d $num] |
||||||
|
set value "" |
||||||
|
|
||||||
|
# We may have already part of the data in |
||||||
|
# memory. Process that piece before looking for more. |
||||||
|
|
||||||
|
incr where |
||||||
|
set have [expr {$n - $where}] |
||||||
|
if {$num < $have} { |
||||||
|
# More than enough in the buffer. |
||||||
|
|
||||||
|
set end $where |
||||||
|
incr end $num |
||||||
|
incr end -1 |
||||||
|
|
||||||
|
if {[Complete $token [string range $input $where $end]]} {return} |
||||||
|
|
||||||
|
set where $end ;# Further processing behind the string. |
||||||
|
set current intro |
||||||
|
|
||||||
|
} elseif {$num == $have} { |
||||||
|
# Just enough. |
||||||
|
|
||||||
|
if {[Complete $token [string range $input $where end]]} {return} |
||||||
|
|
||||||
|
set where $n |
||||||
|
set current intro |
||||||
|
} else { |
||||||
|
# Not enough. Initialize value with the data we |
||||||
|
# have (after the colon) and stop processing for |
||||||
|
# now. |
||||||
|
|
||||||
|
set value [string range $input $where end] |
||||||
|
set current data |
||||||
|
set get $num |
||||||
|
set input "" |
||||||
|
return |
||||||
|
} |
||||||
|
} else { |
||||||
|
return -code error "Syntax error in string length,\ |
||||||
|
expected digit, or ':', got \"$ch\"" |
||||||
|
} |
||||||
|
incr where |
||||||
|
} else { |
||||||
|
# unknown state = internal error |
||||||
|
return -code error "Unknown decoder state \"$current\", internal error" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set input "" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::Command -- |
||||||
|
# |
||||||
|
# Internal helper. Runs the decoder command callback. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# token The decoder invoking its callback |
||||||
|
# how Which method to invoke (value, error, eof) |
||||||
|
# args Arguments for the method. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A boolean flag. Set if further processing has to stop. |
||||||
|
|
||||||
|
proc ::bee::Command {token how args} { |
||||||
|
variable $token |
||||||
|
upvar 0 $token state |
||||||
|
|
||||||
|
#puts Report/$token/$how/$args/ |
||||||
|
|
||||||
|
set cmd $state(cmd) |
||||||
|
set chan $state(chan) |
||||||
|
|
||||||
|
# We catch the fileevents because they will fail when this is |
||||||
|
# called from the 'Close'. The channel will already be gone in |
||||||
|
# that case. |
||||||
|
|
||||||
|
set stop 0 |
||||||
|
if {($how eq "error") || ($how eq "eof")} { |
||||||
|
variable bee |
||||||
|
|
||||||
|
set stop 1 |
||||||
|
fileevent $chan readable {} |
||||||
|
unset bee($chan) |
||||||
|
unset state |
||||||
|
|
||||||
|
if {$how eq "eof"} { |
||||||
|
#puts \tclosing/$chan |
||||||
|
close $chan |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
lappend cmd $how $token |
||||||
|
foreach a $args {lappend cmd $a} |
||||||
|
uplevel #0 $cmd |
||||||
|
|
||||||
|
if {![info exists state]} { |
||||||
|
# The decoder token was killed by the callback, stop |
||||||
|
# processing. |
||||||
|
set stop 1 |
||||||
|
} |
||||||
|
|
||||||
|
#puts /$stop/[file channels] |
||||||
|
return $stop |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::Complete -- |
||||||
|
# |
||||||
|
# Internal helper. Reports a completed value. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# token The decoder reporting the value. |
||||||
|
# value The value to report. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# A boolean flag. Set if further processing has to stop. |
||||||
|
|
||||||
|
proc ::bee::Complete {token value} { |
||||||
|
variable $token |
||||||
|
upvar 0 $token state |
||||||
|
upvar 0 state(pend) pend |
||||||
|
|
||||||
|
if {[llength $pend]} { |
||||||
|
# The value is part of a container. Add the value to its end |
||||||
|
# and keep processing. |
||||||
|
|
||||||
|
set pend [lreplace $pend end end \ |
||||||
|
[linsert [lindex $pend end] end \ |
||||||
|
$value]] |
||||||
|
|
||||||
|
# Don't stop. |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
# The value is at the top, report it. The callback determines if |
||||||
|
# we keep processing. |
||||||
|
|
||||||
|
return [Command $token value $value] |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::decodeCancel -- |
||||||
|
# |
||||||
|
# Destroys the decoder referenced by the token. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# token The decoder to destroy. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
|
||||||
|
proc ::bee::decodeCancel {token} { |
||||||
|
variable bee |
||||||
|
variable $token |
||||||
|
upvar 0 $token state |
||||||
|
unset bee($state(chan)) |
||||||
|
unset state |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ::bee::decodePush -- |
||||||
|
# |
||||||
|
# Push data into the decoder input buffer. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# token The decoder to extend. |
||||||
|
# string The characters to add. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
|
||||||
|
proc ::bee::decodePush {token string} { |
||||||
|
variable $token |
||||||
|
upvar 0 $token state |
||||||
|
append state(read) $string |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
package provide bee 0.3 |
@ -0,0 +1,4 @@ |
|||||||
|
# Tcl package index file, version 1.1 |
||||||
|
|
||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded bee 0.3 [list source [file join $dir bee.tcl]] |
@ -0,0 +1,556 @@ |
|||||||
|
# bench.tcl -- |
||||||
|
# |
||||||
|
# Management of benchmarks. |
||||||
|
# |
||||||
|
# Copyright (c) 2005-2008 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
||||||
|
# library derived from runbench.tcl application (C) Jeff Hobbs. |
||||||
|
# |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Requisites - Packages and namespace for the commands and data. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package require logger |
||||||
|
package require csv |
||||||
|
package require struct::matrix |
||||||
|
package require report |
||||||
|
|
||||||
|
namespace eval ::bench {} |
||||||
|
namespace eval ::bench::out {} |
||||||
|
|
||||||
|
# @mdgen OWNER: libbench.tcl |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Public API - Benchmark execution |
||||||
|
|
||||||
|
# ::bench::run -- |
||||||
|
# |
||||||
|
# Run a series of benchmarks. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# ... |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Dictionary. |
||||||
|
|
||||||
|
proc ::bench::run {args} { |
||||||
|
log::debug [linsert $args 0 ::bench::run] |
||||||
|
|
||||||
|
# -errors 0|1 default 1, propagate errors in benchmarks |
||||||
|
# -threads <num> default 0, no threads, #threads to use |
||||||
|
# -match <pattern> only run tests matching this pattern |
||||||
|
# -rmatch <pattern> only run tests matching this pattern |
||||||
|
# -iters <num> default 1000, max#iterations for any benchmark |
||||||
|
# -pkgdir <dir> Defaults to nothing, regular bench invokation. |
||||||
|
|
||||||
|
# interps - dict (path -> version) |
||||||
|
# files - list (of files) |
||||||
|
|
||||||
|
# Process arguments ...................................... |
||||||
|
# Defaults first, then overides by the user |
||||||
|
|
||||||
|
set errors 1 ; # Propagate errors |
||||||
|
set threads 0 ; # Do not use threads |
||||||
|
set match {} ; # Do not exclude benchmarks based on glob pattern |
||||||
|
set rmatch {} ; # Do not exclude benchmarks based on regex pattern |
||||||
|
set iters 1000 ; # Limit #iterations for any benchmark |
||||||
|
set pkgdirs {} ; # List of dirs to put in front of auto_path in the |
||||||
|
# bench interpreters. Default: nothing. |
||||||
|
|
||||||
|
while {[string match "-*" [set opt [lindex $args 0]]]} { |
||||||
|
set val [lindex $args 1] |
||||||
|
switch -exact -- $opt { |
||||||
|
-errors { |
||||||
|
if {![string is boolean -strict $val]} { |
||||||
|
return -code error "Expected boolean, got \"$val\"" |
||||||
|
} |
||||||
|
set errors $val |
||||||
|
} |
||||||
|
-threads { |
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $val] || ($val < 0)} { |
||||||
|
return -code error "Expected int >= 0, got \"$val\"" |
||||||
|
} |
||||||
|
set threads [format %d $val] |
||||||
|
} |
||||||
|
-match { |
||||||
|
set match [lindex $args 1] |
||||||
|
} |
||||||
|
-rmatch { |
||||||
|
set rmatch [lindex $args 1] |
||||||
|
} |
||||||
|
-iters { |
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $val] || ($val <= 0)} { |
||||||
|
return -code error "Expected int > 0, got \"$val\"" |
||||||
|
} |
||||||
|
set iters [format %d $val] |
||||||
|
} |
||||||
|
-pkgdir { |
||||||
|
CheckPkgDirArg $val |
||||||
|
lappend pkgdirs $val |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "Unknown option \"$opt\", should -errors, -threads, -match, -rmatch, or -iters" |
||||||
|
} |
||||||
|
} |
||||||
|
set args [lrange $args 2 end] |
||||||
|
} |
||||||
|
if {[llength $args] != 2} { |
||||||
|
return -code error "wrong\#args, should be: ?options? interp files" |
||||||
|
} |
||||||
|
foreach {interps files} $args break |
||||||
|
|
||||||
|
# Run the benchmarks ..................................... |
||||||
|
|
||||||
|
array set DATA {} |
||||||
|
|
||||||
|
if {![llength $pkgdirs]} { |
||||||
|
# No user specified package directories => Simple run. |
||||||
|
foreach {ip ver} $interps { |
||||||
|
Invoke $ip $ver {} ;# DATA etc passed via upvar. |
||||||
|
} |
||||||
|
} else { |
||||||
|
# User specified package directories. |
||||||
|
foreach {ip ver} $interps { |
||||||
|
foreach pkgdir $pkgdirs { |
||||||
|
Invoke $ip $ver $pkgdir ;# DATA etc passed via upvar. |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Benchmark data ... Structure, dict (key -> value) |
||||||
|
# |
||||||
|
# Key || Value |
||||||
|
# ============ ++ ========================================= |
||||||
|
# interp IP -> Version. Shell IP was used to run benchmarks. IP is |
||||||
|
# the path to the shell. |
||||||
|
# |
||||||
|
# desc DESC -> "". DESC is description of an executed benchmark. |
||||||
|
# |
||||||
|
# usec DESC IP -> Result. Result of benchmark DESC when run by the |
||||||
|
# shell IP. Usually time in microseconds, but can be |
||||||
|
# a special code as well (ERR, BAD_RES). |
||||||
|
# ============ ++ ========================================= |
||||||
|
|
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
# ::bench::locate -- |
||||||
|
# |
||||||
|
# Locate interpreters on the pathlist, based on a pattern. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# ... |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# List of paths. |
||||||
|
|
||||||
|
proc ::bench::locate {pattern paths} { |
||||||
|
# Cache of executables already found. |
||||||
|
array set var {} |
||||||
|
set res {} |
||||||
|
|
||||||
|
foreach path $paths { |
||||||
|
foreach ip [glob -nocomplain [file join $path $pattern]] { |
||||||
|
set ip [file normalize $ip] |
||||||
|
|
||||||
|
# Follow soft-links to the actual executable. |
||||||
|
while {[string equal link [file type $ip]]} { |
||||||
|
set link [file readlink $ip] |
||||||
|
if {[string match relative [file pathtype $link]]} { |
||||||
|
set ip [file join [file dirname $ip] $link] |
||||||
|
} else { |
||||||
|
set ip $link |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if { |
||||||
|
[file executable $ip] && ![info exists var($ip)] |
||||||
|
} { |
||||||
|
if {[catch {exec $ip << "exit"} dummy]} { |
||||||
|
log::debug "$ip: $dummy" |
||||||
|
continue |
||||||
|
} |
||||||
|
set var($ip) . |
||||||
|
lappend res $ip |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $res |
||||||
|
} |
||||||
|
|
||||||
|
# ::bench::versions -- |
||||||
|
# |
||||||
|
# Take list of interpreters, find their versions. |
||||||
|
# Removes all interps for which it cannot do so. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# List of interpreters (paths) |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# dictionary: interpreter -> version. |
||||||
|
|
||||||
|
proc ::bench::versions {interps} { |
||||||
|
set res {} |
||||||
|
foreach ip $interps { |
||||||
|
if {[catch { |
||||||
|
exec $ip << {puts [info patchlevel] ; exit} |
||||||
|
} patchlevel]} { |
||||||
|
log::debug "$ip: $patchlevel" |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
lappend res [list $patchlevel $ip] |
||||||
|
} |
||||||
|
|
||||||
|
# -uniq 8.4-ism, replaced with use of array. |
||||||
|
array set tmp {} |
||||||
|
set resx {} |
||||||
|
foreach item [lsort -dictionary -decreasing -index 0 $res] { |
||||||
|
foreach {p ip} $item break |
||||||
|
if {[info exists tmp($p)]} continue |
||||||
|
set tmp($p) . |
||||||
|
lappend resx $ip $p |
||||||
|
} |
||||||
|
|
||||||
|
return $resx |
||||||
|
} |
||||||
|
|
||||||
|
# ::bench::merge -- |
||||||
|
# |
||||||
|
# Take the data of several benchmark runs and merge them into |
||||||
|
# one data set. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# One or more data sets to merge |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The merged data set. |
||||||
|
|
||||||
|
proc ::bench::merge {args} { |
||||||
|
if {[llength $args] == 1} { |
||||||
|
return [lindex $args 0] |
||||||
|
} |
||||||
|
|
||||||
|
array set DATA {} |
||||||
|
foreach data $args { |
||||||
|
array set DATA $data |
||||||
|
} |
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
# ::bench::norm -- |
||||||
|
# |
||||||
|
# Normalize the time data in the dataset, using one of the |
||||||
|
# columns as reference. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# Data to normalize |
||||||
|
# Index of reference column |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The normalized data set. |
||||||
|
|
||||||
|
proc ::bench::norm {data col} { |
||||||
|
|
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $col]} { |
||||||
|
return -code error "Ref.column: Expected integer, but got \"$col\"" |
||||||
|
} |
||||||
|
set col [format %d $col] |
||||||
|
if {$col < 1} { |
||||||
|
return -code error "Ref.column out of bounds" |
||||||
|
} |
||||||
|
|
||||||
|
array set DATA $data |
||||||
|
set ipkeys [array names DATA interp*] |
||||||
|
|
||||||
|
if {$col > [llength $ipkeys]} { |
||||||
|
return -code error "Ref.column out of bounds" |
||||||
|
} |
||||||
|
incr col -1 |
||||||
|
set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1] |
||||||
|
|
||||||
|
foreach key [array names DATA] { |
||||||
|
if {[string match "desc*" $key]} continue |
||||||
|
if {[string match "interp*" $key]} continue |
||||||
|
|
||||||
|
foreach {_ desc ip} $key break |
||||||
|
if {[string equal $ip $refip]} continue |
||||||
|
|
||||||
|
set v $DATA($key) |
||||||
|
if {![string is double -strict $v]} continue |
||||||
|
|
||||||
|
if {![info exists DATA([list usec $desc $refip])]} { |
||||||
|
# We cannot normalize, we do not keep the time value. |
||||||
|
# The row will be shown, empty. |
||||||
|
set DATA($key) "" |
||||||
|
continue |
||||||
|
} |
||||||
|
set vref $DATA([list usec $desc $refip]) |
||||||
|
|
||||||
|
if {![string is double -strict $vref]} continue |
||||||
|
|
||||||
|
set DATA($key) [expr {$v/double($vref)}] |
||||||
|
} |
||||||
|
|
||||||
|
foreach key [array names DATA [list * $refip]] { |
||||||
|
if {![string is double -strict $DATA($key)]} continue |
||||||
|
set DATA($key) 1 |
||||||
|
} |
||||||
|
|
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
# ::bench::edit -- |
||||||
|
# |
||||||
|
# Change the 'path' of an interp to a user-defined value. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# Data to edit |
||||||
|
# Index of column to change |
||||||
|
# The value replacing the current path |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The changed data set. |
||||||
|
|
||||||
|
proc ::bench::edit {data col new} { |
||||||
|
|
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $col]} { |
||||||
|
return -code error "Ref.column: Expected integer, but got \"$col\"" |
||||||
|
} |
||||||
|
set col [format %d $col] |
||||||
|
if {$col < 1} { |
||||||
|
return -code error "Ref.column out of bounds" |
||||||
|
} |
||||||
|
|
||||||
|
array set DATA $data |
||||||
|
set ipkeys [array names DATA interp*] |
||||||
|
|
||||||
|
if {$col > [llength $ipkeys]} { |
||||||
|
return -code error "Ref.column out of bounds" |
||||||
|
} |
||||||
|
incr col -1 |
||||||
|
set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1] |
||||||
|
|
||||||
|
if {[string equal $new $refip]} { |
||||||
|
# No change, quick return |
||||||
|
return $data |
||||||
|
} |
||||||
|
|
||||||
|
set refkey [list interp $refip] |
||||||
|
set DATA([list interp $new]) $DATA($refkey) |
||||||
|
unset DATA($refkey) |
||||||
|
|
||||||
|
foreach key [array names DATA [list * $refip]] { |
||||||
|
if {![string equal [lindex $key 0] "usec"]} continue |
||||||
|
foreach {__ desc ip} $key break |
||||||
|
set DATA([list usec $desc $new]) $DATA($key) |
||||||
|
unset DATA($key) |
||||||
|
} |
||||||
|
|
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
# ::bench::del -- |
||||||
|
# |
||||||
|
# Remove the data for an interp. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# Data to edit |
||||||
|
# Index of column to remove |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The changed data set. |
||||||
|
|
||||||
|
proc ::bench::del {data col} { |
||||||
|
##nagelfar ignore |
||||||
|
if {![string is integer -strict $col]} { |
||||||
|
return -code error "Ref.column: Expected integer, but got \"$col\"" |
||||||
|
} |
||||||
|
set col [format %d $col] |
||||||
|
if {$col < 1} { |
||||||
|
return -code error "Ref.column out of bounds" |
||||||
|
} |
||||||
|
|
||||||
|
array set DATA $data |
||||||
|
set ipkeys [array names DATA interp*] |
||||||
|
|
||||||
|
if {$col > [llength $ipkeys]} { |
||||||
|
return -code error "Ref.column out of bounds" |
||||||
|
} |
||||||
|
incr col -1 |
||||||
|
set refip [lindex [lindex [lsort -dict $ipkeys] $col] 1] |
||||||
|
|
||||||
|
unset DATA([list interp $refip]) |
||||||
|
|
||||||
|
# Do not use 'array unset'. Keep 8.2 clean. |
||||||
|
foreach key [array names DATA [list * $refip]] { |
||||||
|
if {![string equal [lindex $key 0] "usec"]} continue |
||||||
|
unset DATA($key) |
||||||
|
} |
||||||
|
|
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Public API - Result formatting. |
||||||
|
|
||||||
|
# ::bench::out::raw -- |
||||||
|
# |
||||||
|
# Format the result of a benchmark run. |
||||||
|
# Style: Raw data. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# DATA dict |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# String containing the formatted DATA. |
||||||
|
|
||||||
|
proc ::bench::out::raw {data} { |
||||||
|
return $data |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Internal commands |
||||||
|
|
||||||
|
proc ::bench::CheckPkgDirArg {path {expected {}}} { |
||||||
|
# Allow empty string, special. |
||||||
|
if {![string length $path]} return |
||||||
|
|
||||||
|
if {![file isdirectory $path]} { |
||||||
|
return -code error \ |
||||||
|
"The path \"$path\" is not a directory." |
||||||
|
} |
||||||
|
if {![file readable $path]} { |
||||||
|
return -code error \ |
||||||
|
"The path \"$path\" is not readable." |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bench::Invoke {ip ver pkgdir} { |
||||||
|
variable self |
||||||
|
# Import remainder of the current configuration/settings. |
||||||
|
|
||||||
|
upvar 1 DATA DATA match match rmatch rmatch \ |
||||||
|
iters iters errors errors threads threads \ |
||||||
|
files files |
||||||
|
|
||||||
|
if {[string length $pkgdir]} { |
||||||
|
log::info "Benchmark $ver ($pkgdir) $ip" |
||||||
|
set idstr "$ip ($pkgdir)" |
||||||
|
} else { |
||||||
|
log::info "Benchmark $ver $ip" |
||||||
|
set idstr $ip |
||||||
|
} |
||||||
|
|
||||||
|
set DATA([list interp $idstr]) $ver |
||||||
|
|
||||||
|
set cmd [list $ip [file join $self libbench.tcl] \ |
||||||
|
-match $match \ |
||||||
|
-rmatch $rmatch \ |
||||||
|
-iters $iters \ |
||||||
|
-interp $ip \ |
||||||
|
-errors $errors \ |
||||||
|
-threads $threads \ |
||||||
|
-pkgdir $pkgdir \ |
||||||
|
] |
||||||
|
|
||||||
|
# Determine elapsed time per file, logged. |
||||||
|
set start [clock seconds] |
||||||
|
|
||||||
|
array set tmp {} |
||||||
|
|
||||||
|
if {$threads} { |
||||||
|
foreach f $files { lappend cmd $f } |
||||||
|
if {[catch { |
||||||
|
close [Process [open |$cmd r+]] |
||||||
|
} output]} { |
||||||
|
if {$errors} { |
||||||
|
error $::errorInfo |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
foreach file $files { |
||||||
|
log::info [file tail $file] |
||||||
|
if {[catch { |
||||||
|
close [Process [open |[linsert $cmd end $file] r+]] |
||||||
|
} output]} { |
||||||
|
if {$errors} { |
||||||
|
error $::errorInfo |
||||||
|
} else { |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach desc [array names tmp] { |
||||||
|
set DATA([list desc $desc]) {} |
||||||
|
set DATA([list usec $desc $idstr]) $tmp($desc) |
||||||
|
} |
||||||
|
|
||||||
|
unset tmp |
||||||
|
set elapsed [expr {[clock seconds] - $start}] |
||||||
|
|
||||||
|
set hour [expr {$elapsed / 3600}] |
||||||
|
set min [expr {$elapsed / 60}] |
||||||
|
set sec [expr {$elapsed % 60}] |
||||||
|
log::info " [format %.2d:%.2d:%.2d $hour $min $sec] elapsed" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc ::bench::Process {pipe} { |
||||||
|
while {1} { |
||||||
|
if {[eof $pipe]} break |
||||||
|
if {[gets $pipe line] < 0} break |
||||||
|
# AK: FUTURE: Log all lines?! |
||||||
|
#puts |$line| |
||||||
|
set line [string trim $line] |
||||||
|
if {[string equal $line ""]} continue |
||||||
|
|
||||||
|
Result |
||||||
|
Feedback |
||||||
|
# Unknown lines are printed. Future: Callback?! |
||||||
|
log::info $line |
||||||
|
} |
||||||
|
return $pipe |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bench::Result {} { |
||||||
|
upvar 1 line line |
||||||
|
if {[lindex $line 0] ne "RESULT"} return |
||||||
|
upvar 2 tmp tmp |
||||||
|
foreach {_ desc result} $line break |
||||||
|
set tmp($desc) $result |
||||||
|
return -code continue |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bench::Feedback {} { |
||||||
|
upvar 1 line line |
||||||
|
if {[lindex $line 0] ne "LOG"} return |
||||||
|
# AK: Future - Run through callback?! |
||||||
|
log::info [lindex $line 1] |
||||||
|
return -code continue |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Initialize internal data structures. |
||||||
|
|
||||||
|
namespace eval ::bench { |
||||||
|
variable self [file join [pwd] [file dirname [info script]]] |
||||||
|
|
||||||
|
logger::init bench |
||||||
|
logger::import -force -all -namespace log bench |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Ready to run |
||||||
|
|
||||||
|
package provide bench 0.6 |
@ -0,0 +1,162 @@ |
|||||||
|
# bench_read.tcl -- |
||||||
|
# |
||||||
|
# Management of benchmarks, reading results in various formats. |
||||||
|
# |
||||||
|
# Copyright (c) 2005 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
||||||
|
# library derived from runbench.tcl application (C) Jeff Hobbs. |
||||||
|
# |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: bench_read.tcl,v 1.3 2006/06/13 23:20:30 andreas_kupries Exp $ |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Requisites - Packages and namespace for the commands and data. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package require csv |
||||||
|
|
||||||
|
namespace eval ::bench::in {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Public API - Result reading |
||||||
|
|
||||||
|
# ::bench::in::read -- |
||||||
|
# |
||||||
|
# Read a bench result in any of the raw/csv/text formats |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# path to file to read |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# DATA dictionary, internal representation of the bench results. |
||||||
|
|
||||||
|
proc ::bench::in::read {file} { |
||||||
|
|
||||||
|
set f [open $file r] |
||||||
|
set head [gets $f] |
||||||
|
|
||||||
|
if {![string match "# -\\*- tcl -\\*- bench/*" $head]} { |
||||||
|
return -code error "Bad file format, not a benchmark file" |
||||||
|
} else { |
||||||
|
regexp {bench/(.*)$} $head -> format |
||||||
|
|
||||||
|
switch -exact -- $format { |
||||||
|
raw - csv - text { |
||||||
|
set res [RD$format $f] |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "Bad format \"$val\", expected text, csv, or raw" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
close $f |
||||||
|
return $res |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Internal commands |
||||||
|
|
||||||
|
proc ::bench::in::RDraw {chan} { |
||||||
|
return [string trimright [::read $chan]] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bench::in::RDcsv {chan} { |
||||||
|
# Lines Format |
||||||
|
# First line is number of interpreters #n. int |
||||||
|
# Next to 1+n is interpreter data. id,ver,path |
||||||
|
# Beyond is benchmark results. id,desc,res1,...,res#n |
||||||
|
|
||||||
|
array set DATA {} |
||||||
|
|
||||||
|
# #Interp ... |
||||||
|
|
||||||
|
set nip [lindex [csv::split [gets $chan]] 0] |
||||||
|
|
||||||
|
# Interp data ... |
||||||
|
|
||||||
|
set iplist {} |
||||||
|
for {set i 0} {$i < $nip} {incr i} { |
||||||
|
foreach {__ ver ip} [csv::split [gets $chan]] break |
||||||
|
|
||||||
|
set DATA([list interp $ip]) $ver |
||||||
|
lappend iplist $ip |
||||||
|
} |
||||||
|
|
||||||
|
# Benchmark data ... |
||||||
|
|
||||||
|
while {[gets $chan line] >= 0} { |
||||||
|
set line [string trim $line] |
||||||
|
if {$line == {}} break |
||||||
|
set line [csv::split $line] |
||||||
|
set desc [lindex $line 1] |
||||||
|
|
||||||
|
set DATA([list desc $desc]) {} |
||||||
|
foreach val [lrange $line 2 end] ip $iplist { |
||||||
|
if {$val == {}} continue |
||||||
|
set DATA([list usec $desc $ip]) $val |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bench::in::RDtext {chan} { |
||||||
|
array set DATA {} |
||||||
|
|
||||||
|
# Interp data ... |
||||||
|
|
||||||
|
# Empty line - ignore |
||||||
|
# "id: ver path" - interp data. |
||||||
|
# Empty line - separator before benchmark data. |
||||||
|
|
||||||
|
set n 0 |
||||||
|
set iplist {} |
||||||
|
while {[gets $chan line] >= 0} { |
||||||
|
set line [string trim $line] |
||||||
|
if {$line == {}} { |
||||||
|
incr n |
||||||
|
if {$n == 2} break |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
regexp {[^:]+: ([^ ]+) (.*)$} $line -> ver ip |
||||||
|
set DATA([list interp $ip]) $ver |
||||||
|
lappend iplist $ip |
||||||
|
} |
||||||
|
|
||||||
|
# Benchmark data ... |
||||||
|
|
||||||
|
# '---' -> Ignore. |
||||||
|
# '|' column separators. Remove spaces around it. Then treat line |
||||||
|
# as CSV data with a particular separator. |
||||||
|
# Ignore the INTERP line. |
||||||
|
|
||||||
|
while {[gets $chan line] >= 0} { |
||||||
|
set line [string trim $line] |
||||||
|
if {$line == {}} continue |
||||||
|
if {[string match "+---*" $line]} continue |
||||||
|
if {[string match "*INTERP*" $line]} continue |
||||||
|
|
||||||
|
regsub -all "\\| +" $line {|} line |
||||||
|
regsub -all " +\\|" $line {|} line |
||||||
|
set line [csv::split [string trim $line |] |] |
||||||
|
set desc [lindex $line 1] |
||||||
|
|
||||||
|
set DATA([list desc $desc]) {} |
||||||
|
foreach val [lrange $line 2 end] ip $iplist { |
||||||
|
if {$val == {}} continue |
||||||
|
set DATA([list usec $desc $ip]) $val |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return [array get DATA] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Initialize internal data structures. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Ready to run |
||||||
|
|
||||||
|
package provide bench::in 0.2 |
@ -0,0 +1,101 @@ |
|||||||
|
# bench_wtext.tcl -- |
||||||
|
# |
||||||
|
# Management of benchmarks, formatted text. |
||||||
|
# |
||||||
|
# Copyright (c) 2005 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
||||||
|
# library derived from runbench.tcl application (C) Jeff Hobbs. |
||||||
|
# |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: bench_wcsv.tcl,v 1.4 2007/01/21 23:29:06 andreas_kupries Exp $ |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Requisites - Packages and namespace for the commands and data. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package require csv |
||||||
|
|
||||||
|
namespace eval ::bench::out {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Public API - Benchmark execution |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Public API - Result formatting. |
||||||
|
|
||||||
|
# ::bench::out::csv -- |
||||||
|
# |
||||||
|
# Format the result of a benchmark run. |
||||||
|
# Style: CSV |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# DATA dict |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# String containing the formatted DATA. |
||||||
|
|
||||||
|
proc ::bench::out::csv {data} { |
||||||
|
array set DATA $data |
||||||
|
set CSV {} |
||||||
|
|
||||||
|
# 1st record: #shells |
||||||
|
# 2nd record to #shells+1: Interpreter data (id, version, path) |
||||||
|
# #shells+2 to end: Benchmark data (id,desc,result1,...,result#shells) |
||||||
|
|
||||||
|
# --- --- ---- |
||||||
|
# #interpreters used |
||||||
|
|
||||||
|
set ipkeys [array names DATA interp*] |
||||||
|
lappend CSV [csv::join [list [llength $ipkeys]]] |
||||||
|
|
||||||
|
# --- --- ---- |
||||||
|
# Table 1: Interpreter information. |
||||||
|
|
||||||
|
set n 1 |
||||||
|
set iplist {} |
||||||
|
foreach key [lsort -dict $ipkeys] { |
||||||
|
set ip [lindex $key 1] |
||||||
|
lappend CSV [csv::join [list $n $DATA($key) $ip]] |
||||||
|
set DATA($key) $n |
||||||
|
incr n |
||||||
|
lappend iplist $ip |
||||||
|
} |
||||||
|
|
||||||
|
# --- --- ---- |
||||||
|
# Table 2: Benchmark information |
||||||
|
|
||||||
|
set dlist {} |
||||||
|
foreach key [lsort -dict -index 1 [array names DATA desc*]] { |
||||||
|
lappend dlist [lindex $key 1] |
||||||
|
} |
||||||
|
|
||||||
|
set n 1 |
||||||
|
foreach desc $dlist { |
||||||
|
set record {} |
||||||
|
lappend record $n |
||||||
|
lappend record $desc |
||||||
|
foreach ip $iplist { |
||||||
|
if {[catch { |
||||||
|
lappend record $DATA([list usec $desc $ip]) |
||||||
|
}]} { |
||||||
|
lappend record {} |
||||||
|
} |
||||||
|
} |
||||||
|
lappend CSV [csv::join $record] |
||||||
|
incr n |
||||||
|
} |
||||||
|
|
||||||
|
return [join $CSV \n] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Internal commands |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Initialize internal data structures. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Ready to run |
||||||
|
|
||||||
|
package provide bench::out::csv 0.1.3 |
@ -0,0 +1,165 @@ |
|||||||
|
# bench_wtext.tcl -- |
||||||
|
# |
||||||
|
# Management of benchmarks, formatted text. |
||||||
|
# |
||||||
|
# Copyright (c) 2005 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
||||||
|
# library derived from runbench.tcl application (C) Jeff Hobbs. |
||||||
|
# |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: bench_wtext.tcl,v 1.4 2007/01/21 23:29:06 andreas_kupries Exp $ |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Requisites - Packages and namespace for the commands and data. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package require struct::matrix |
||||||
|
package require report |
||||||
|
|
||||||
|
namespace eval ::bench::out {} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Public API - Result formatting. |
||||||
|
|
||||||
|
# ::bench::out::text -- |
||||||
|
# |
||||||
|
# Format the result of a benchmark run. |
||||||
|
# Style: TEXT |
||||||
|
# |
||||||
|
# General structure like CSV, but nicely formatted and aligned |
||||||
|
# columns. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# DATA dict |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# String containing the formatted DATA. |
||||||
|
|
||||||
|
proc ::bench::out::text {data} { |
||||||
|
array set DATA $data |
||||||
|
set LINES {} |
||||||
|
|
||||||
|
# 1st line to #shells: Interpreter data (id, version, path) |
||||||
|
# #shells+1 to end: Benchmark data (id,desc,result1,...,result#shells) |
||||||
|
|
||||||
|
lappend LINES {} |
||||||
|
|
||||||
|
# --- --- ---- |
||||||
|
# Table 1: Interpreter information. |
||||||
|
|
||||||
|
set ipkeys [array names DATA interp*] |
||||||
|
set n 1 |
||||||
|
set iplist {} |
||||||
|
set vlen 0 |
||||||
|
foreach key [lsort -dict $ipkeys] { |
||||||
|
lappend iplist [lindex $key 1] |
||||||
|
incr n |
||||||
|
set l [string length $DATA($key)] |
||||||
|
if {$l > $vlen} {set vlen $l} |
||||||
|
} |
||||||
|
set idlen [string length $n] |
||||||
|
|
||||||
|
set dlist {} |
||||||
|
set n 1 |
||||||
|
foreach key [lsort -dict -index 1 [array names DATA desc*]] { |
||||||
|
lappend dlist [lindex $key 1] |
||||||
|
incr n |
||||||
|
} |
||||||
|
set didlen [string length $n] |
||||||
|
|
||||||
|
set n 1 |
||||||
|
set record [list "" INTERP] |
||||||
|
foreach ip $iplist { |
||||||
|
set v $DATA([list interp $ip]) |
||||||
|
lappend LINES " [PADL $idlen $n]: [PADR $vlen $v] $ip" |
||||||
|
lappend record $n |
||||||
|
incr n |
||||||
|
} |
||||||
|
|
||||||
|
lappend LINES {} |
||||||
|
|
||||||
|
# --- --- ---- |
||||||
|
# Table 2: Benchmark information |
||||||
|
|
||||||
|
set m [struct::matrix m] |
||||||
|
$m add columns [expr {2 + [llength $iplist]}] |
||||||
|
$m add row $record |
||||||
|
|
||||||
|
set n 1 |
||||||
|
foreach desc $dlist { |
||||||
|
set record [list $n] |
||||||
|
lappend record $desc |
||||||
|
|
||||||
|
foreach ip $iplist { |
||||||
|
if {[catch { |
||||||
|
set val $DATA([list usec $desc $ip]) |
||||||
|
}]} { |
||||||
|
set val {} |
||||||
|
} |
||||||
|
if {[string is double -strict $val]} { |
||||||
|
lappend record [format %.2f $val] |
||||||
|
} else { |
||||||
|
lappend record [format %s $val] |
||||||
|
} |
||||||
|
} |
||||||
|
$m add row $record |
||||||
|
incr n |
||||||
|
} |
||||||
|
|
||||||
|
::report::defstyle simpletable {} { |
||||||
|
data set [split "[string repeat "| " [columns]]|"] |
||||||
|
top set [split "[string repeat "+ - " [columns]]+"] |
||||||
|
bottom set [top get] |
||||||
|
top enable |
||||||
|
bottom enable |
||||||
|
|
||||||
|
set c [columns] |
||||||
|
justify 0 right |
||||||
|
pad 0 both |
||||||
|
|
||||||
|
if {$c > 1} { |
||||||
|
justify 1 left |
||||||
|
pad 1 both |
||||||
|
} |
||||||
|
for {set i 2} {$i < $c} {incr i} { |
||||||
|
justify $i right |
||||||
|
pad $i both |
||||||
|
} |
||||||
|
} |
||||||
|
::report::defstyle captionedtable {{n 1}} { |
||||||
|
simpletable |
||||||
|
topdata set [data get] |
||||||
|
topcapsep set [top get] |
||||||
|
topcapsep enable |
||||||
|
tcaption $n |
||||||
|
} |
||||||
|
|
||||||
|
set r [report::report r [$m columns] style captionedtable] |
||||||
|
lappend LINES [$m format 2string $r] |
||||||
|
$m destroy |
||||||
|
$r destroy |
||||||
|
|
||||||
|
return [join $LINES \n] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Internal commands |
||||||
|
|
||||||
|
proc ::bench::out::PADL {max str} { |
||||||
|
format "%${max}s" $str |
||||||
|
#return "[PAD $max $str]$str" |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bench::out::PADR {max str} { |
||||||
|
format "%-${max}s" $str |
||||||
|
#return "$str[PAD $max $str]" |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Initialize internal data structures. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### ########################### |
||||||
|
## Ready to run |
||||||
|
|
||||||
|
package provide bench::out::text 0.1.3 |
@ -0,0 +1,561 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# libbench.tcl ?(<option> <value>)...? <benchFile>... |
||||||
|
# |
||||||
|
# This file has to have code that works in any version of Tcl that |
||||||
|
# the user would want to benchmark. |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: libbench.tcl,v 1.4 2008/07/02 23:34:06 andreas_kupries Exp $ |
||||||
|
# |
||||||
|
# Copyright (c) 2000-2001 Jeffrey Hobbs. |
||||||
|
# Copyright (c) 2007 Andreas Kupries |
||||||
|
# |
||||||
|
|
||||||
|
# This code provides the supporting commands for the execution of a |
||||||
|
# benchmark files. It is actually an application and is exec'd by the |
||||||
|
# management code. |
||||||
|
|
||||||
|
# Options: |
||||||
|
# -help Print usage message. |
||||||
|
# -rmatch <regexp-pattern> Run only tests whose description matches the pattern. |
||||||
|
# -match <glob-pattern> Run only tests whose description matches the pattern. |
||||||
|
# -interp <name> Name of the interp running the benchmarks. |
||||||
|
# -thread <num> Invoke threaded benchmarks, number of threads to use. |
||||||
|
# -errors <boolean> Throw errors, or not. |
||||||
|
|
||||||
|
# Note: If both -match and -rmatch are specified then _both_ |
||||||
|
# apply. I.e. a benchmark will be run if and only if it matches both |
||||||
|
# patterns. |
||||||
|
|
||||||
|
# Application activity and results are communicated to the highlevel |
||||||
|
# management via text written to stdout. Each line written is a list |
||||||
|
# and has one of the following forms: |
||||||
|
# |
||||||
|
# __THREADED <version> - Indicates threaded mode, and version |
||||||
|
# of package Thread in use. |
||||||
|
# |
||||||
|
# Sourcing {<desc>: <res>} - Benchmark <desc> has started. |
||||||
|
# <res> is the result from executing |
||||||
|
# it once (compilation of body.) |
||||||
|
# |
||||||
|
# Sourcing <file> - Benchmark file <file> starts execution. |
||||||
|
# |
||||||
|
# <desc> <res> - Result of a benchmark. |
||||||
|
# |
||||||
|
# The above implies that no benchmark may use the strings 'Sourcing' |
||||||
|
# or '__THREADED' as their description. |
||||||
|
|
||||||
|
# We will put our data into these named globals. |
||||||
|
|
||||||
|
global BENCH bench |
||||||
|
|
||||||
|
# 'BENCH' contents: |
||||||
|
# |
||||||
|
# - ERRORS : Boolean flag. If set benchmark output mismatches are |
||||||
|
# reported by throwing an error. Otherwise they are simply |
||||||
|
# listed as BAD_RES. Default true. Can be set/reset via |
||||||
|
# option -errors. |
||||||
|
# |
||||||
|
# - MATCH : Match pattern, see -match, default empty, aka everything |
||||||
|
# matches. |
||||||
|
# |
||||||
|
# - RMATCH : Match pattern, see -rmatch, default empty, aka |
||||||
|
# everything matches. |
||||||
|
# |
||||||
|
# - OUTFILE : Name of output file, default is special value "stdout". |
||||||
|
# - OUTFID : Channel for output. |
||||||
|
# |
||||||
|
# The outfile cannot be set by the caller, thus output is always |
||||||
|
# written to stdout. |
||||||
|
# |
||||||
|
# - FILES : List of benchmark files to run. |
||||||
|
# |
||||||
|
# - ITERS : Number of iterations to run a benchmark body, default |
||||||
|
# 1000. Can be overridden by the individual benchmarks. |
||||||
|
# |
||||||
|
# - THREADS : Number of threads to use. 0 signals no threading. |
||||||
|
# Limited to number of files if there are less files than |
||||||
|
# requested threads. |
||||||
|
# |
||||||
|
# - EXIT : Boolean flag. True when appplication is run by wish, for |
||||||
|
# special exit processing. ... Actually always true. |
||||||
|
# |
||||||
|
# - INTERP : Name of the interpreter running the benchmarks. Is the |
||||||
|
# executable running this code. Can be overridden via the |
||||||
|
# command line option -interp. |
||||||
|
# |
||||||
|
# - uniqid : Counter for 'bench_tmpfile' to generate unique names of |
||||||
|
# tmp files. |
||||||
|
# |
||||||
|
# - us : Thread id of main thread. |
||||||
|
# |
||||||
|
# - inuse : Number of threads active, present and relevant only in |
||||||
|
# threaded mode. |
||||||
|
# |
||||||
|
# - file : Currently executed benchmark file. Relevant only in |
||||||
|
# non-threaded mode. |
||||||
|
|
||||||
|
# |
||||||
|
# 'bench' contents. |
||||||
|
|
||||||
|
# Benchmark results, mapping from the benchmark descriptions to their |
||||||
|
# results. Usually time in microseconds, but the following special |
||||||
|
# values can occur: |
||||||
|
# |
||||||
|
# - BAD_RES - Result from benchmark body does not match expectations. |
||||||
|
# - ERR - Benchmark body aborted with an error. |
||||||
|
# - Any string - Forced by error code 666 to pass to management. |
||||||
|
|
||||||
|
# |
||||||
|
# We claim all procedures starting with bench* |
||||||
|
# |
||||||
|
|
||||||
|
# bench_tmpfile -- |
||||||
|
# |
||||||
|
# Return a temp file name that can be modified at will |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# None |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns file name |
||||||
|
# |
||||||
|
proc bench_tmpfile {} { |
||||||
|
global tcl_platform env BENCH |
||||||
|
if {![info exists BENCH(uniqid)]} { set BENCH(uniqid) 0 } |
||||||
|
set base "tclbench[incr BENCH(uniqid)].dat" |
||||||
|
if {[info exists tcl_platform(platform)]} { |
||||||
|
if {$tcl_platform(platform) == "unix"} { |
||||||
|
return "/tmp/$base" |
||||||
|
} elseif {$tcl_platform(platform) == "windows"} { |
||||||
|
return [file join $env(TEMP) $base] |
||||||
|
} else { |
||||||
|
return $base |
||||||
|
} |
||||||
|
} else { |
||||||
|
# The Good Ol' Days (?) when only Unix support existed |
||||||
|
return "/tmp/$base" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# bench_rm -- |
||||||
|
# |
||||||
|
# Remove a file silently (no complaining) |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# args Files to delete |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns nothing |
||||||
|
# |
||||||
|
proc bench_rm {args} { |
||||||
|
foreach file $args { |
||||||
|
if {[info tclversion] > 7.4} { |
||||||
|
catch {file delete $file} |
||||||
|
} else { |
||||||
|
catch {exec /bin/rm $file} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc bench_puts {args} { |
||||||
|
eval [linsert $args 0 FEEDBACK] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# bench -- |
||||||
|
# |
||||||
|
# Main bench procedure. |
||||||
|
# The bench test is expected to exit cleanly. If an error occurs, |
||||||
|
# it will be thrown all the way up. A bench proc may return the |
||||||
|
# special code 666, which says take the string as the bench value. |
||||||
|
# This is usually used for N/A feature situations. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# |
||||||
|
# -pre script to run before main timed body |
||||||
|
# -body script to run as main timed body |
||||||
|
# -post script to run after main timed body |
||||||
|
# -ipre script to run before timed body, per iteration of the body. |
||||||
|
# -ipost script to run after timed body, per iteration of the body. |
||||||
|
# -desc message text |
||||||
|
# -iterations <#> |
||||||
|
# |
||||||
|
# Note: |
||||||
|
# |
||||||
|
# Using -ipre and/or -ipost will cause us to compute the average |
||||||
|
# time ourselves, i.e. 'time body 1' n times. Required to ensure |
||||||
|
# that prefix/post operation are executed, yet not timed themselves. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# |
||||||
|
# Returns nothing |
||||||
|
# |
||||||
|
# Side effects: |
||||||
|
# |
||||||
|
# Sets up data in bench global array |
||||||
|
# |
||||||
|
proc bench {args} { |
||||||
|
global BENCH bench errorInfo errorCode |
||||||
|
|
||||||
|
# -pre script |
||||||
|
# -body script |
||||||
|
# -desc msg |
||||||
|
# -post script |
||||||
|
# -ipre script |
||||||
|
# -ipost script |
||||||
|
# -iterations <#> |
||||||
|
array set opts { |
||||||
|
-pre {} |
||||||
|
-body {} |
||||||
|
-desc {} |
||||||
|
-post {} |
||||||
|
-ipre {} |
||||||
|
-ipost {} |
||||||
|
} |
||||||
|
set opts(-iter) $BENCH(ITERS) |
||||||
|
while {[llength $args]} { |
||||||
|
set key [lindex $args 0] |
||||||
|
switch -glob -- $key { |
||||||
|
-res* { set opts(-res) [lindex $args 1] } |
||||||
|
-pr* { set opts(-pre) [lindex $args 1] } |
||||||
|
-po* { set opts(-post) [lindex $args 1] } |
||||||
|
-ipr* { set opts(-ipre) [lindex $args 1] } |
||||||
|
-ipo* { set opts(-ipost) [lindex $args 1] } |
||||||
|
-bo* { set opts(-body) [lindex $args 1] } |
||||||
|
-de* { set opts(-desc) [lindex $args 1] } |
||||||
|
-it* { |
||||||
|
# Only change the iterations when it is smaller than |
||||||
|
# the requested default |
||||||
|
set val [lindex $args 1] |
||||||
|
if {$opts(-iter) > $val} { set opts(-iter) $val } |
||||||
|
} |
||||||
|
default { |
||||||
|
error "unknown option $key" |
||||||
|
} |
||||||
|
} |
||||||
|
set args [lreplace $args 0 1] |
||||||
|
} |
||||||
|
|
||||||
|
FEEDBACK "Running <$opts(-desc)>" |
||||||
|
|
||||||
|
if {($BENCH(MATCH) != "") && ![string match $BENCH(MATCH) $opts(-desc)]} { |
||||||
|
return |
||||||
|
} |
||||||
|
if {($BENCH(RMATCH) != "") && ![regexp $BENCH(RMATCH) $opts(-desc)]} { |
||||||
|
return |
||||||
|
} |
||||||
|
if {$opts(-pre) != ""} { |
||||||
|
uplevel \#0 $opts(-pre) |
||||||
|
} |
||||||
|
if {$opts(-body) != ""} { |
||||||
|
# always run it once to remove compile phase confusion |
||||||
|
if {$opts(-ipre) != ""} { |
||||||
|
uplevel \#0 $opts(-ipre) |
||||||
|
} |
||||||
|
set code [catch {uplevel \#0 $opts(-body)} res] |
||||||
|
if {$opts(-ipost) != ""} { |
||||||
|
uplevel \#0 $opts(-ipost) |
||||||
|
} |
||||||
|
if {!$code && [info exists opts(-res)] \ |
||||||
|
&& [string compare $opts(-res) $res]} { |
||||||
|
if {$BENCH(ERRORS)} { |
||||||
|
return -code error "Result was:\n$res\nResult\ |
||||||
|
should have been:\n$opts(-res)" |
||||||
|
} else { |
||||||
|
set res "BAD_RES" |
||||||
|
} |
||||||
|
#set bench($opts(-desc)) $res |
||||||
|
RESULT $opts(-desc) $res |
||||||
|
} else { |
||||||
|
if {($opts(-ipre) != "") || ($opts(-ipost) != "")} { |
||||||
|
# We do the averaging on our own, to allow untimed |
||||||
|
# pre/post execution per iteration. We catch and |
||||||
|
# handle problems in the pre/post code as if |
||||||
|
# everything was executed as one block (like it would |
||||||
|
# be in the other path). We are using floating point |
||||||
|
# to avoid integer overflow, easily happening when |
||||||
|
# accumulating a high number (iterations) of large |
||||||
|
# integers (microseconds). |
||||||
|
|
||||||
|
set total 0.0 |
||||||
|
for {set i 0} {$i < $opts(-iter)} {incr i} { |
||||||
|
set code 0 |
||||||
|
if {$opts(-ipre) != ""} { |
||||||
|
set code [catch {uplevel \#0 $opts(-ipre)} res] |
||||||
|
if {$code} break |
||||||
|
} |
||||||
|
set code [catch {uplevel \#0 [list time $opts(-body) 1]} res] |
||||||
|
if {$code} break |
||||||
|
set total [expr {$total + [lindex $res 0]}] |
||||||
|
if {$opts(-ipost) != ""} { |
||||||
|
set code [catch {uplevel \#0 $opts(-ipost)} res] |
||||||
|
if {$code} break |
||||||
|
} |
||||||
|
} |
||||||
|
if {!$code} { |
||||||
|
set res [list [expr {int ($total/$opts(-iter))}] microseconds per iteration] |
||||||
|
} |
||||||
|
} else { |
||||||
|
set code [catch {uplevel \#0 \ |
||||||
|
[list time $opts(-body) $opts(-iter)]} res] |
||||||
|
} |
||||||
|
if {!$BENCH(THREADS)} { |
||||||
|
if {$code == 0} { |
||||||
|
# Get just the microseconds value from the time result |
||||||
|
set res [lindex $res 0] |
||||||
|
} elseif {$code != 666} { |
||||||
|
# A 666 result code means pass it through to the bench |
||||||
|
# suite. Otherwise throw errors all the way out, unless |
||||||
|
# we specified not to throw errors (option -errors 0 to |
||||||
|
# libbench). |
||||||
|
if {$BENCH(ERRORS)} { |
||||||
|
return -code $code -errorinfo $errorInfo \ |
||||||
|
-errorcode $errorCode |
||||||
|
} else { |
||||||
|
set res "ERR" |
||||||
|
} |
||||||
|
} |
||||||
|
#set bench($opts(-desc)) $res |
||||||
|
RESULT $opts(-desc) $res |
||||||
|
} else { |
||||||
|
# Threaded runs report back asynchronously |
||||||
|
thread::send $BENCH(us) \ |
||||||
|
[list thread_report $opts(-desc) $code $res] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if {($opts(-post) != "") && [catch {uplevel \#0 $opts(-post)} err] \ |
||||||
|
&& $BENCH(ERRORS)} { |
||||||
|
return -code error "post code threw error:\n$err" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc RESULT {desc time} { |
||||||
|
global BENCH |
||||||
|
puts $BENCH(OUTFID) [list RESULT $desc $time] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc FEEDBACK {text} { |
||||||
|
global BENCH |
||||||
|
puts $BENCH(OUTFID) [list LOG $text] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc usage {} { |
||||||
|
set me [file tail [info script]] |
||||||
|
puts stderr "Usage: $me ?options?\ |
||||||
|
\n\t-help # print out this message\ |
||||||
|
\n\t-rmatch <regexp> # only run tests matching this pattern\ |
||||||
|
\n\t-match <glob> # only run tests matching this pattern\ |
||||||
|
\n\t-interp <name> # name of interp (tries to get it right)\ |
||||||
|
\n\t-thread <num> # number of threads to use\ |
||||||
|
\n\tfileList # files to benchmark" |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
# |
||||||
|
# Process args |
||||||
|
# |
||||||
|
if {[catch {set BENCH(INTERP) [info nameofexec]}]} { |
||||||
|
set BENCH(INTERP) $argv0 |
||||||
|
} |
||||||
|
foreach {var val} { |
||||||
|
ERRORS 1 |
||||||
|
MATCH {} |
||||||
|
RMATCH {} |
||||||
|
OUTFILE stdout |
||||||
|
FILES {} |
||||||
|
ITERS 1000 |
||||||
|
THREADS 0 |
||||||
|
PKGDIR {} |
||||||
|
EXIT "[info exists tk_version]" |
||||||
|
} { |
||||||
|
if {![info exists BENCH($var)]} { |
||||||
|
set BENCH($var) [subst $val] |
||||||
|
} |
||||||
|
} |
||||||
|
set BENCH(EXIT) 1 |
||||||
|
|
||||||
|
if {[llength $argv]} { |
||||||
|
while {[llength $argv]} { |
||||||
|
set key [lindex $argv 0] |
||||||
|
switch -glob -- $key { |
||||||
|
-help* { usage } |
||||||
|
-err* { set BENCH(ERRORS) [lindex $argv 1] } |
||||||
|
-int* { set BENCH(INTERP) [lindex $argv 1] } |
||||||
|
-rmat* { set BENCH(RMATCH) [lindex $argv 1] } |
||||||
|
-mat* { set BENCH(MATCH) [lindex $argv 1] } |
||||||
|
-iter* { set BENCH(ITERS) [lindex $argv 1] } |
||||||
|
-thr* { set BENCH(THREADS) [lindex $argv 1] } |
||||||
|
-pkg* { set BENCH(PKGDIR) [lindex $argv 1] } |
||||||
|
default { |
||||||
|
foreach arg $argv { |
||||||
|
if {![file exists $arg]} { usage } |
||||||
|
lappend BENCH(FILES) $arg |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
set argv [lreplace $argv 0 1] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {[string length $BENCH(PKGDIR)]} { |
||||||
|
set auto_path [linsert $auto_path 0 $BENCH(PKGDIR)] |
||||||
|
} |
||||||
|
|
||||||
|
if {$BENCH(THREADS)} { |
||||||
|
# We have to be able to load threads if we want to use threads, and |
||||||
|
# we don't want to create more threads than we have files. |
||||||
|
if {[catch {package require Thread}]} { |
||||||
|
set BENCH(THREADS) 0 |
||||||
|
} elseif {[llength $BENCH(FILES)] < $BENCH(THREADS)} { |
||||||
|
set BENCH(THREADS) [llength $BENCH(FILES)] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
rename exit exit.true |
||||||
|
proc exit args { |
||||||
|
error "called \"exit $args\" in benchmark test" |
||||||
|
} |
||||||
|
|
||||||
|
if {[string compare $BENCH(OUTFILE) stdout]} { |
||||||
|
set BENCH(OUTFID) [open $BENCH(OUTFILE) w] |
||||||
|
} else { |
||||||
|
set BENCH(OUTFID) stdout |
||||||
|
} |
||||||
|
|
||||||
|
# |
||||||
|
# Everything that gets output must be in pairwise format, because |
||||||
|
# the data will be collected in via an 'array set'. |
||||||
|
# |
||||||
|
|
||||||
|
if {$BENCH(THREADS)} { |
||||||
|
# Each file must run in it's own thread because of all the extra |
||||||
|
# header stuff they have. |
||||||
|
#set DEBUG 1 |
||||||
|
proc thread_one {{id 0}} { |
||||||
|
global BENCH |
||||||
|
set file [lindex $BENCH(FILES) 0] |
||||||
|
set BENCH(FILES) [lrange $BENCH(FILES) 1 end] |
||||||
|
if {[file exists $file]} { |
||||||
|
incr BENCH(inuse) |
||||||
|
FEEDBACK [list Sourcing $file] |
||||||
|
if {$id} { |
||||||
|
set them $id |
||||||
|
} else { |
||||||
|
set them [thread::create] |
||||||
|
thread::send -async $them { load {} Thread } |
||||||
|
thread::send -async $them \ |
||||||
|
[list array set BENCH [array get BENCH]] |
||||||
|
thread::send -async $them \ |
||||||
|
[list proc bench_tmpfile {} [info body bench_tmpfile]] |
||||||
|
thread::send -async $them \ |
||||||
|
[list proc bench_rm {args} [info body bench_rm]] |
||||||
|
thread::send -async $them \ |
||||||
|
[list proc bench {args} [info body bench]] |
||||||
|
} |
||||||
|
if {[info exists ::DEBUG]} { |
||||||
|
FEEDBACK "SEND [clock seconds] thread $them $file INUSE\ |
||||||
|
$BENCH(inuse) of $BENCH(THREADS)" |
||||||
|
} |
||||||
|
thread::send -async $them [list source $file] |
||||||
|
thread::send -async $them \ |
||||||
|
[list thread::send $BENCH(us) [list thread_ready $them]] |
||||||
|
#thread::send -async $them { thread::unwind } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc thread_em {} { |
||||||
|
global BENCH |
||||||
|
while {[llength $BENCH(FILES)]} { |
||||||
|
if {[info exists ::DEBUG]} { |
||||||
|
FEEDBACK "THREAD ONE [lindex $BENCH(FILES) 0]" |
||||||
|
} |
||||||
|
thread_one |
||||||
|
if {$BENCH(inuse) >= $BENCH(THREADS)} { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc thread_ready {id} { |
||||||
|
global BENCH |
||||||
|
|
||||||
|
incr BENCH(inuse) -1 |
||||||
|
if {[llength $BENCH(FILES)]} { |
||||||
|
if {[info exists ::DEBUG]} { |
||||||
|
FEEDBACK "SEND ONE [clock seconds] thread $id" |
||||||
|
} |
||||||
|
thread_one $id |
||||||
|
} else { |
||||||
|
if {[info exists ::DEBUG]} { |
||||||
|
FEEDBACK "UNWIND thread $id" |
||||||
|
} |
||||||
|
thread::send -async $id { thread::unwind } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc thread_report {desc code res} { |
||||||
|
global BENCH bench errorInfo errorCode |
||||||
|
|
||||||
|
if {$code == 0} { |
||||||
|
# Get just the microseconds value from the time result |
||||||
|
set res [lindex $res 0] |
||||||
|
} elseif {$code != 666} { |
||||||
|
# A 666 result code means pass it through to the bench suite. |
||||||
|
# Otherwise throw errors all the way out, unless we specified |
||||||
|
# not to throw errors (option -errors 0 to libbench). |
||||||
|
if {$BENCH(ERRORS)} { |
||||||
|
return -code $code -errorinfo $errorInfo \ |
||||||
|
-errorcode $errorCode |
||||||
|
} else { |
||||||
|
set res "ERR" |
||||||
|
} |
||||||
|
} |
||||||
|
#set bench($desc) $res |
||||||
|
RESULT $desc $res |
||||||
|
} |
||||||
|
|
||||||
|
proc thread_finish {{delay 4000}} { |
||||||
|
global BENCH bench |
||||||
|
set val [expr {[llength [thread::names]] > 1}] |
||||||
|
#set val [expr {$BENCH(inuse)}] |
||||||
|
if {$val} { |
||||||
|
after $delay [info level 0] |
||||||
|
} else { |
||||||
|
if {0} {foreach desc [array names bench] { |
||||||
|
RESULT $desc $bench($desc) |
||||||
|
}} |
||||||
|
if {$BENCH(EXIT)} { |
||||||
|
exit.true ; # needed for Tk tests |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set BENCH(us) [thread::id] |
||||||
|
set BENCH(inuse) 0 ; # num threads in use |
||||||
|
FEEDBACK [list __THREADED [package provide Thread]] |
||||||
|
|
||||||
|
thread_em |
||||||
|
thread_finish |
||||||
|
vwait forever |
||||||
|
} else { |
||||||
|
foreach BENCH(file) $BENCH(FILES) { |
||||||
|
if {[file exists $BENCH(file)]} { |
||||||
|
FEEDBACK [list Sourcing $BENCH(file)] |
||||||
|
source $BENCH(file) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {0} {foreach desc [array names bench] { |
||||||
|
RESULT $desc $bench($desc) |
||||||
|
}} |
||||||
|
|
||||||
|
if {$BENCH(EXIT)} { |
||||||
|
exit.true ; # needed for Tk tests |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} { |
||||||
|
return |
||||||
|
} |
||||||
|
package ifneeded bench 0.6 [list source [file join $dir bench.tcl]] |
||||||
|
package ifneeded bench::out::text 0.1.3 [list source [file join $dir bench_wtext.tcl]] |
||||||
|
package ifneeded bench::out::csv 0.1.3 [list source [file join $dir bench_wcsv.tcl]] |
||||||
|
package ifneeded bench::in 0.2 [list source [file join $dir bench_read.tcl]] |
@ -0,0 +1,501 @@ |
|||||||
|
##### |
||||||
|
# |
||||||
|
# "BibTeX parser" |
||||||
|
# http://wiki.tcl.tk/13719 |
||||||
|
# |
||||||
|
# Tcl code harvested on: 7 Mar 2005, 23:55 GMT |
||||||
|
# Wiki page last updated: ??? |
||||||
|
# |
||||||
|
##### |
||||||
|
|
||||||
|
# bibtex.tcl -- |
||||||
|
# |
||||||
|
# A basic parser for BibTeX bibliography databases. |
||||||
|
# |
||||||
|
# Copyright (c) 2005 Neil Madden. |
||||||
|
# Copyright (c) 2005 Andreas Kupries. |
||||||
|
# License: Tcl/BSD style. |
||||||
|
|
||||||
|
### NOTES |
||||||
|
### |
||||||
|
### Need commands to introspect parser state. Especially the string |
||||||
|
### map (for testing of 'addStrings', should be useful in general as |
||||||
|
### well). |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Requisites |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package require cmdline |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Implementation: Public API |
||||||
|
|
||||||
|
namespace eval ::bibtex {} |
||||||
|
|
||||||
|
# bibtex::parse -- |
||||||
|
# |
||||||
|
# Parse a bibtex file. |
||||||
|
# |
||||||
|
# parse ?options? ?bibtex? |
||||||
|
|
||||||
|
proc ::bibtex::parse {args} { |
||||||
|
variable data |
||||||
|
variable id |
||||||
|
|
||||||
|
# Argument processing |
||||||
|
if {[llength $args] < 1} { |
||||||
|
set err "[lindex [info level 0] 0] ?options? ?bibtex?" |
||||||
|
return -code error "wrong # args: should be \"$err\"" |
||||||
|
} |
||||||
|
|
||||||
|
array set state {} |
||||||
|
GetOptions $args state |
||||||
|
|
||||||
|
# Initialize the parser state from the options, fill in default |
||||||
|
# values, and handle the input according the specified mode. |
||||||
|
|
||||||
|
set token bibtex[incr id] |
||||||
|
foreach {k v} [array get state] { |
||||||
|
set data($token,$k) $v |
||||||
|
} |
||||||
|
|
||||||
|
if {$state(stream)} { |
||||||
|
# Text not in memory |
||||||
|
if {!$state(bg)} { |
||||||
|
# Text from a channel, no async processing. We read everything |
||||||
|
# into memory and the handle it as before. |
||||||
|
|
||||||
|
set blockmode [fconfigure $state(-channel) -blocking] |
||||||
|
fconfigure $state(-channel) -blocking 1 |
||||||
|
set data($token,buffer) [read $state(-channel)] |
||||||
|
fconfigure $state(-channel) -blocking $blockmode |
||||||
|
|
||||||
|
# Tell upcoming processing that the text is in memory. |
||||||
|
set state(stream) 0 |
||||||
|
} else { |
||||||
|
# Text from a channel, and processing is async. Create an |
||||||
|
# event handler for the incoming data. |
||||||
|
|
||||||
|
set data($token,done) 0 |
||||||
|
fileevent $state(-channel) readable \ |
||||||
|
[list ::bibtex::ReadChan $token] |
||||||
|
|
||||||
|
# Initialize the parser internal result buffer if we use plain |
||||||
|
# -command, and not the SAX api. |
||||||
|
if {!$state(sax)} { |
||||||
|
set data($token,result) {} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Initialize the string mappings (none known), and the result |
||||||
|
# accumulator. |
||||||
|
set data($token,strings) {} |
||||||
|
set data($token,result) {} |
||||||
|
|
||||||
|
if {!$state(stream)} { |
||||||
|
ParseRecords $token 1 |
||||||
|
if {$state(sax)} { |
||||||
|
set result $token |
||||||
|
} else { |
||||||
|
set result $data($token,result) |
||||||
|
destroy $token |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# Assert: Processing is in background. |
||||||
|
return $token |
||||||
|
} |
||||||
|
|
||||||
|
# Cleanup a parser, cancelling any callbacks etc. |
||||||
|
|
||||||
|
proc ::bibtex::destroy {token} { |
||||||
|
variable data |
||||||
|
|
||||||
|
if {![info exists data($token,stream)]} { |
||||||
|
return -code error "Illegal bibtex parser \"$token\"" |
||||||
|
} |
||||||
|
if {$data($token,stream)} { |
||||||
|
fileevent $data($token,-channel) readable {} |
||||||
|
} |
||||||
|
|
||||||
|
array unset data $token,* |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc ::bibtex::wait {token} { |
||||||
|
variable data |
||||||
|
|
||||||
|
if {![info exists data($token,stream)]} { |
||||||
|
return -code error "Illegal bibtex parser \"$token\"" |
||||||
|
} |
||||||
|
vwait ::bibtex::data($token,done) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# bibtex::addStrings -- |
||||||
|
# |
||||||
|
# Add strings to the map for a particular parser. All strings are |
||||||
|
# expanded at parse time. |
||||||
|
|
||||||
|
proc ::bibtex::addStrings {token strings} { |
||||||
|
variable data |
||||||
|
eval [linsert $strings 0 lappend data($token,strings)] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Implementation: Private utility routines |
||||||
|
|
||||||
|
proc ::bibtex::AddRecord {token type key recdata} { |
||||||
|
variable data |
||||||
|
lappend data($token,result) [list $type $key $recdata] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::GetOptions {argv statevar} { |
||||||
|
upvar 1 $statevar state |
||||||
|
|
||||||
|
# Basic processing of the argument list |
||||||
|
# and the options found therein. |
||||||
|
|
||||||
|
set opts [lrange [::cmdline::GetOptionDefaults { |
||||||
|
{command.arg {}} |
||||||
|
{channel.arg {}} |
||||||
|
{recordcommand.arg {}} |
||||||
|
{preamblecommand.arg {}} |
||||||
|
{stringcommand.arg {}} |
||||||
|
{commentcommand.arg {}} |
||||||
|
{progresscommand.arg {}} |
||||||
|
{casesensitivestrings.arg {}} |
||||||
|
} result] 2 end] ;# Remove ? and help. |
||||||
|
|
||||||
|
set argc [llength $argv] |
||||||
|
while {[set err [::cmdline::getopt argv $opts opt arg]]} { |
||||||
|
if {$err < 0} { |
||||||
|
set olist "" |
||||||
|
foreach o [lsort $opts] { |
||||||
|
if {[string match *.arg $o]} { |
||||||
|
set o [string range $o 0 end-4] |
||||||
|
} |
||||||
|
lappend olist -$o |
||||||
|
} |
||||||
|
return -code error "bad option \"$opt\",\ |
||||||
|
should be one of\ |
||||||
|
[linsert [join $olist ", "] end-1 or]" |
||||||
|
} |
||||||
|
set state(-$opt) $arg |
||||||
|
} |
||||||
|
|
||||||
|
# Check the information gained so far |
||||||
|
# for inconsistencies and/or missing |
||||||
|
# pieces. |
||||||
|
|
||||||
|
set sax [expr { |
||||||
|
[info exists state(-recordcommand)] || |
||||||
|
[info exists state(-preamblecommand)] || |
||||||
|
[info exists state(-stringcommand)] || |
||||||
|
[info exists state(-commentcommand)] || |
||||||
|
[info exists state(-progresscommand)] |
||||||
|
}] ; # {} |
||||||
|
|
||||||
|
set bg [info exists state(-command)] |
||||||
|
|
||||||
|
if {$sax && $bg} { |
||||||
|
# Sax callbacks and channel completion callback exclude each |
||||||
|
# other. |
||||||
|
return -code error "The options -command and -TYPEcommand exclude each other" |
||||||
|
} |
||||||
|
|
||||||
|
set stream [info exists state(-channel)] |
||||||
|
|
||||||
|
if {$stream} { |
||||||
|
# Channel is present, a text is not allowed. |
||||||
|
if {[llength $argv]} { |
||||||
|
return -code error "Option -channel and text exclude each other" |
||||||
|
} |
||||||
|
|
||||||
|
# The channel has to exist as well. |
||||||
|
if {[lsearch -exact [file channels] $state(-channel)] < 0} { |
||||||
|
return -code error "Illegal channel handle \"$state(-channel)\"" |
||||||
|
} |
||||||
|
} else { |
||||||
|
# Channel is not present, we have to have a text, and only |
||||||
|
# exactly one. And a general -command callback is not allowed. |
||||||
|
|
||||||
|
if {![llength $argv]} { |
||||||
|
return -code error "Neither -channel nor text specified" |
||||||
|
} elseif {[llength $argv] > 1} { |
||||||
|
return -code error "wrong # args: [lindex [info level 1] 0] ?options? ?bibtex?" |
||||||
|
} |
||||||
|
|
||||||
|
# Channel completion callback is not allowed if we are not |
||||||
|
# reading from a channel. |
||||||
|
|
||||||
|
if {$bg} { |
||||||
|
return -code error "Option -command and text exclude each other" |
||||||
|
} |
||||||
|
|
||||||
|
set state(buffer) [lindex $argv 0] |
||||||
|
} |
||||||
|
|
||||||
|
set state(stream) $stream |
||||||
|
set state(sax) $sax |
||||||
|
set state(bg) [expr {$sax || $bg}] |
||||||
|
|
||||||
|
if {![info exists state(-stringcommand)]} { |
||||||
|
set state(-stringcommand) [list ::bibtex::addStrings] |
||||||
|
} |
||||||
|
if {![info exists state(-recordcommand)] && (!$sax)} { |
||||||
|
set state(-recordcommand) [list ::bibtex::AddRecord] |
||||||
|
} |
||||||
|
if {[info exists state(-casesensitivestrings)] && |
||||||
|
$state(-casesensitivestrings) |
||||||
|
} { |
||||||
|
set state(casesensitivestrings) 1 |
||||||
|
} else { |
||||||
|
set state(casesensitivestrings) 0 |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::Callback {token type args} { |
||||||
|
variable data |
||||||
|
|
||||||
|
#puts stdout "Callback ($token $type ($args))" |
||||||
|
|
||||||
|
if {[info exists data($token,-${type}command)]} { |
||||||
|
eval $data($token,-${type}command) [linsert $args 0 $token] |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::ReadChan {token} { |
||||||
|
variable data |
||||||
|
|
||||||
|
# Read the waiting characters into our buffer and process |
||||||
|
# them. The records are saved either through a user supplied |
||||||
|
# record callback, or the standard callback for our non-sax |
||||||
|
# processing. |
||||||
|
|
||||||
|
set chan $data($token,-channel) |
||||||
|
append data($token,buffer) [read $chan] |
||||||
|
|
||||||
|
if {[eof $chan]} { |
||||||
|
# Final processing. In non-SAX mode we have to deliver the |
||||||
|
# completed result before destroying the parser. |
||||||
|
|
||||||
|
ParseRecords $token 1 |
||||||
|
set data($token,done) 1 |
||||||
|
if {!$data($token,sax)} { |
||||||
|
Callback $token {} $data($token,result) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# Processing of partial data. |
||||||
|
|
||||||
|
ParseRecords $token 0 |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::Tidy {str} { |
||||||
|
return [string tolower [string trim $str]] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::ParseRecords {token eof} { |
||||||
|
# A rough BibTeX grammar (case-insensitive): |
||||||
|
# |
||||||
|
# Database ::= (Junk '@' Entry)* |
||||||
|
# Junk ::= .*? |
||||||
|
# Entry ::= Record |
||||||
|
# | Comment |
||||||
|
# | String |
||||||
|
# | Preamble |
||||||
|
# Comment ::= "comment" [^\n]* \n -- ignored |
||||||
|
# String ::= "string" '{' Field* '}' |
||||||
|
# Preamble ::= "preamble" '{' .* '}' -- (balanced) |
||||||
|
# Record ::= Type '{' Key ',' Field* '}' |
||||||
|
# | Type '(' Key ',' Field* ')' -- not handled |
||||||
|
# Type ::= Name |
||||||
|
# Key ::= Name |
||||||
|
# Field ::= Name '=' Value |
||||||
|
# Name ::= [^\s\"#%'(){}]* |
||||||
|
# Value ::= [0-9]+ |
||||||
|
# | '"' ([^'"']|\\'"')* '"' |
||||||
|
# | '{' .* '}' -- (balanced) |
||||||
|
|
||||||
|
# " - Fixup emacs hilit confusion from the grammar above. |
||||||
|
variable data |
||||||
|
set bibtex $data($token,buffer) |
||||||
|
|
||||||
|
# Split at each @ character which is at the beginning of a line, |
||||||
|
# modulo whitespace. This is a heuristic to distinguish the @'s |
||||||
|
# starting a new record from the @'s occuring inside a record, as |
||||||
|
# part of email addresses. Empty pices at beginning or end are |
||||||
|
# stripped before the split. |
||||||
|
|
||||||
|
regsub -line -all {^[\n\r\f\t ]*@} $bibtex \000 bibtex |
||||||
|
set db [split [string trim $bibtex \000] \000] |
||||||
|
|
||||||
|
if {$eof} { |
||||||
|
set total [llength $db] |
||||||
|
set step [expr {double($total) / 100.0}] |
||||||
|
set istep [expr {$step > 1 ? int($step) : 1}] |
||||||
|
set count 0 |
||||||
|
} else { |
||||||
|
if {[llength $db] < 2} { |
||||||
|
# Nothing to process, or data which ay be incomplete. |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
set data($token,buffer) [lindex $db end] |
||||||
|
set db [lrange $db 0 end-1] |
||||||
|
|
||||||
|
# Fake progress meter. |
||||||
|
set count -1 |
||||||
|
} |
||||||
|
|
||||||
|
foreach block $db { |
||||||
|
if {$count < 0} { |
||||||
|
Callback $token progress -1 |
||||||
|
} elseif {([incr count] % $istep) == 0} { |
||||||
|
Callback $token progress [expr {int($count / $step)}] |
||||||
|
} |
||||||
|
if {[regexp -nocase {\s*comment([^\n])*\n(.*)} $block \ |
||||||
|
-> cmnt rest]} { |
||||||
|
# Are @comments blocks, or just 1 line? |
||||||
|
# Does anyone care? |
||||||
|
Callback $token comment $cmnt |
||||||
|
|
||||||
|
} elseif {[regexp -nocase {^\s*string[^\{]*\{(.*)\}[^\}]*} \ |
||||||
|
$block -> rest]} { |
||||||
|
# string macro defs |
||||||
|
if {$data($token,casesensitivestrings)} { |
||||||
|
Callback $token string [ParseString $rest] |
||||||
|
} else { |
||||||
|
Callback $token string [ParseBlock $rest] |
||||||
|
} |
||||||
|
} elseif {[regexp -nocase {\s*preamble[^\{]*\{(.*)\}[^\}]*} \ |
||||||
|
$block -> rest]} { |
||||||
|
Callback $token preamble $rest |
||||||
|
|
||||||
|
} elseif {[regexp {([^\{]+)\{([^,]*),(.*)\}[^\}]*} \ |
||||||
|
$block -> type key rest]} { |
||||||
|
# Do any @string mappings |
||||||
|
if {$data($token,casesensitivestrings)} { |
||||||
|
# puts $data($token,strings) |
||||||
|
set rest [string map $data($token,strings) $rest] |
||||||
|
} else { |
||||||
|
set rest [string map -nocase $data($token,strings) $rest] |
||||||
|
} |
||||||
|
Callback $token record [Tidy $type] [string trim $key] \ |
||||||
|
[ParseBlock $rest] |
||||||
|
} else { |
||||||
|
## FUTURE: Use a logger. |
||||||
|
puts stderr "Skipping: $block" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::ParseString {block} { |
||||||
|
regexp {(\S+)[^=]*=(.*)} $block -> key rest |
||||||
|
return [list $key $rest] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::ParseBlock {block} { |
||||||
|
set ret [list] |
||||||
|
set index 0 |
||||||
|
while { |
||||||
|
[regexp -start $index -indices -- \ |
||||||
|
{(\S+)\s*=(.*)} $block -> key rest] |
||||||
|
} { |
||||||
|
foreach {ks ke} $key break |
||||||
|
set k [Tidy [string range $block $ks $ke]] |
||||||
|
foreach {rs re} $rest break |
||||||
|
foreach {v index} \ |
||||||
|
[ParseBibString $rs [string range $block $rs $re]] \ |
||||||
|
break |
||||||
|
lappend ret $k $v |
||||||
|
} |
||||||
|
return $ret |
||||||
|
} |
||||||
|
|
||||||
|
proc ::bibtex::ParseBibString {index str} { |
||||||
|
set count 0 |
||||||
|
set retstr "" |
||||||
|
set escape 0 |
||||||
|
set string 0 |
||||||
|
foreach char [split $str ""] { |
||||||
|
incr index |
||||||
|
if {$escape} { |
||||||
|
set escape 0 |
||||||
|
} else { |
||||||
|
if {$char eq "\{"} { |
||||||
|
incr count |
||||||
|
continue |
||||||
|
} elseif {$char eq "\}"} { |
||||||
|
incr count -1 |
||||||
|
if {$count < 0} {incr index -1; break} |
||||||
|
continue |
||||||
|
} elseif {$char eq ","} { |
||||||
|
if {$count == 0} break |
||||||
|
} elseif {$char eq "\\"} { |
||||||
|
set escape 1 |
||||||
|
continue |
||||||
|
} elseif {$char eq "\""} { |
||||||
|
# Handling the case where str is surrounded by |
||||||
|
# quotation marks instead of braces (as some journals |
||||||
|
# may, perhaps erroneously, print some field. e.g.: |
||||||
|
# https://www.epj.org/) |
||||||
|
if {$count == 0} { |
||||||
|
incr count |
||||||
|
} elseif {$count == 1} { |
||||||
|
incr count -1 |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
# else: Nothing |
||||||
|
} |
||||||
|
append retstr $char |
||||||
|
} |
||||||
|
regsub -all {\s+} $retstr { } retstr |
||||||
|
return [list [string trim $retstr] $index] |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Internal. Package configuration and state. |
||||||
|
|
||||||
|
namespace eval bibtex { |
||||||
|
# Counter for the generation of parser tokens. |
||||||
|
variable id 0 |
||||||
|
|
||||||
|
# State of all parsers. Keys for each parser are prefixed with the |
||||||
|
# parser token. |
||||||
|
variable data |
||||||
|
array set data {} |
||||||
|
|
||||||
|
# Keys and their meaning (listed without token prefix) |
||||||
|
## |
||||||
|
# buffer |
||||||
|
# eof |
||||||
|
# channel <-\/- Difference ? |
||||||
|
# strings | |
||||||
|
# -async | |
||||||
|
# -blocksize | |
||||||
|
# -channel <-/ |
||||||
|
# -recordcommand -- callback for each record |
||||||
|
# -preamblecommand -- callback for @preamble blocks |
||||||
|
# -stringcommand -- callback for @string macros |
||||||
|
# -commentcommand -- callback for @comment blocks |
||||||
|
# -progresscommand -- callback to indicate progress of parse |
||||||
|
## |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ready to go |
||||||
|
package provide bibtex 0.8 |
||||||
|
# EOF |
@ -0,0 +1,2 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded bibtex 0.8 [list source [file join $dir bibtex.tcl]] |
@ -0,0 +1,755 @@ |
|||||||
|
# blowfish.tcl - |
||||||
|
# |
||||||
|
# Pure-Tcl implementation of the Blowfish algorithm. |
||||||
|
# |
||||||
|
# See http://www.schneier.com/blowfish.html for information about the |
||||||
|
# Blowfish algorithm. |
||||||
|
# |
||||||
|
# The implementation is derived from Paul Kocher's implementation, |
||||||
|
# available at http://www.schneier.com/blowfish-download.html |
||||||
|
# |
||||||
|
# Copyright (C) 2004 Frank Pilhofer |
||||||
|
# Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net> |
||||||
|
# |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
|
||||||
|
namespace eval blowfish { |
||||||
|
variable uid |
||||||
|
if {![info exists uid]} { set uid 0 } |
||||||
|
|
||||||
|
variable accel |
||||||
|
array set accel {trf 0} |
||||||
|
|
||||||
|
namespace export blowfish |
||||||
|
|
||||||
|
variable ORIG_P { |
||||||
|
0x243F6A88 0x85A308D3 0x13198A2E 0x03707344 |
||||||
|
0xA4093822 0x299F31D0 0x082EFA98 0xEC4E6C89 |
||||||
|
0x452821E6 0x38D01377 0xBE5466CF 0x34E90C6C |
||||||
|
0xC0AC29B7 0xC97C50DD 0x3F84D5B5 0xB5470917 |
||||||
|
0x9216D5D9 0x8979FB1B |
||||||
|
} |
||||||
|
|
||||||
|
variable ORIG_S { |
||||||
|
0xD1310BA6 0x98DFB5AC 0x2FFD72DB 0xD01ADFB7 |
||||||
|
0xB8E1AFED 0x6A267E96 0xBA7C9045 0xF12C7F99 |
||||||
|
0x24A19947 0xB3916CF7 0x0801F2E2 0x858EFC16 |
||||||
|
0x636920D8 0x71574E69 0xA458FEA3 0xF4933D7E |
||||||
|
0x0D95748F 0x728EB658 0x718BCD58 0x82154AEE |
||||||
|
0x7B54A41D 0xC25A59B5 0x9C30D539 0x2AF26013 |
||||||
|
0xC5D1B023 0x286085F0 0xCA417918 0xB8DB38EF |
||||||
|
0x8E79DCB0 0x603A180E 0x6C9E0E8B 0xB01E8A3E |
||||||
|
0xD71577C1 0xBD314B27 0x78AF2FDA 0x55605C60 |
||||||
|
0xE65525F3 0xAA55AB94 0x57489862 0x63E81440 |
||||||
|
0x55CA396A 0x2AAB10B6 0xB4CC5C34 0x1141E8CE |
||||||
|
0xA15486AF 0x7C72E993 0xB3EE1411 0x636FBC2A |
||||||
|
0x2BA9C55D 0x741831F6 0xCE5C3E16 0x9B87931E |
||||||
|
0xAFD6BA33 0x6C24CF5C 0x7A325381 0x28958677 |
||||||
|
0x3B8F4898 0x6B4BB9AF 0xC4BFE81B 0x66282193 |
||||||
|
0x61D809CC 0xFB21A991 0x487CAC60 0x5DEC8032 |
||||||
|
0xEF845D5D 0xE98575B1 0xDC262302 0xEB651B88 |
||||||
|
0x23893E81 0xD396ACC5 0x0F6D6FF3 0x83F44239 |
||||||
|
0x2E0B4482 0xA4842004 0x69C8F04A 0x9E1F9B5E |
||||||
|
0x21C66842 0xF6E96C9A 0x670C9C61 0xABD388F0 |
||||||
|
0x6A51A0D2 0xD8542F68 0x960FA728 0xAB5133A3 |
||||||
|
0x6EEF0B6C 0x137A3BE4 0xBA3BF050 0x7EFB2A98 |
||||||
|
0xA1F1651D 0x39AF0176 0x66CA593E 0x82430E88 |
||||||
|
0x8CEE8619 0x456F9FB4 0x7D84A5C3 0x3B8B5EBE |
||||||
|
0xE06F75D8 0x85C12073 0x401A449F 0x56C16AA6 |
||||||
|
0x4ED3AA62 0x363F7706 0x1BFEDF72 0x429B023D |
||||||
|
0x37D0D724 0xD00A1248 0xDB0FEAD3 0x49F1C09B |
||||||
|
0x075372C9 0x80991B7B 0x25D479D8 0xF6E8DEF7 |
||||||
|
0xE3FE501A 0xB6794C3B 0x976CE0BD 0x04C006BA |
||||||
|
0xC1A94FB6 0x409F60C4 0x5E5C9EC2 0x196A2463 |
||||||
|
0x68FB6FAF 0x3E6C53B5 0x1339B2EB 0x3B52EC6F |
||||||
|
0x6DFC511F 0x9B30952C 0xCC814544 0xAF5EBD09 |
||||||
|
0xBEE3D004 0xDE334AFD 0x660F2807 0x192E4BB3 |
||||||
|
0xC0CBA857 0x45C8740F 0xD20B5F39 0xB9D3FBDB |
||||||
|
0x5579C0BD 0x1A60320A 0xD6A100C6 0x402C7279 |
||||||
|
0x679F25FE 0xFB1FA3CC 0x8EA5E9F8 0xDB3222F8 |
||||||
|
0x3C7516DF 0xFD616B15 0x2F501EC8 0xAD0552AB |
||||||
|
0x323DB5FA 0xFD238760 0x53317B48 0x3E00DF82 |
||||||
|
0x9E5C57BB 0xCA6F8CA0 0x1A87562E 0xDF1769DB |
||||||
|
0xD542A8F6 0x287EFFC3 0xAC6732C6 0x8C4F5573 |
||||||
|
0x695B27B0 0xBBCA58C8 0xE1FFA35D 0xB8F011A0 |
||||||
|
0x10FA3D98 0xFD2183B8 0x4AFCB56C 0x2DD1D35B |
||||||
|
0x9A53E479 0xB6F84565 0xD28E49BC 0x4BFB9790 |
||||||
|
0xE1DDF2DA 0xA4CB7E33 0x62FB1341 0xCEE4C6E8 |
||||||
|
0xEF20CADA 0x36774C01 0xD07E9EFE 0x2BF11FB4 |
||||||
|
0x95DBDA4D 0xAE909198 0xEAAD8E71 0x6B93D5A0 |
||||||
|
0xD08ED1D0 0xAFC725E0 0x8E3C5B2F 0x8E7594B7 |
||||||
|
0x8FF6E2FB 0xF2122B64 0x8888B812 0x900DF01C |
||||||
|
0x4FAD5EA0 0x688FC31C 0xD1CFF191 0xB3A8C1AD |
||||||
|
0x2F2F2218 0xBE0E1777 0xEA752DFE 0x8B021FA1 |
||||||
|
0xE5A0CC0F 0xB56F74E8 0x18ACF3D6 0xCE89E299 |
||||||
|
0xB4A84FE0 0xFD13E0B7 0x7CC43B81 0xD2ADA8D9 |
||||||
|
0x165FA266 0x80957705 0x93CC7314 0x211A1477 |
||||||
|
0xE6AD2065 0x77B5FA86 0xC75442F5 0xFB9D35CF |
||||||
|
0xEBCDAF0C 0x7B3E89A0 0xD6411BD3 0xAE1E7E49 |
||||||
|
0x00250E2D 0x2071B35E 0x226800BB 0x57B8E0AF |
||||||
|
0x2464369B 0xF009B91E 0x5563911D 0x59DFA6AA |
||||||
|
0x78C14389 0xD95A537F 0x207D5BA2 0x02E5B9C5 |
||||||
|
0x83260376 0x6295CFA9 0x11C81968 0x4E734A41 |
||||||
|
0xB3472DCA 0x7B14A94A 0x1B510052 0x9A532915 |
||||||
|
0xD60F573F 0xBC9BC6E4 0x2B60A476 0x81E67400 |
||||||
|
0x08BA6FB5 0x571BE91F 0xF296EC6B 0x2A0DD915 |
||||||
|
0xB6636521 0xE7B9F9B6 0xFF34052E 0xC5855664 |
||||||
|
0x53B02D5D 0xA99F8FA1 0x08BA4799 0x6E85076A |
||||||
|
0x4B7A70E9 0xB5B32944 0xDB75092E 0xC4192623 |
||||||
|
0xAD6EA6B0 0x49A7DF7D 0x9CEE60B8 0x8FEDB266 |
||||||
|
0xECAA8C71 0x699A17FF 0x5664526C 0xC2B19EE1 |
||||||
|
0x193602A5 0x75094C29 0xA0591340 0xE4183A3E |
||||||
|
0x3F54989A 0x5B429D65 0x6B8FE4D6 0x99F73FD6 |
||||||
|
0xA1D29C07 0xEFE830F5 0x4D2D38E6 0xF0255DC1 |
||||||
|
0x4CDD2086 0x8470EB26 0x6382E9C6 0x021ECC5E |
||||||
|
0x09686B3F 0x3EBAEFC9 0x3C971814 0x6B6A70A1 |
||||||
|
0x687F3584 0x52A0E286 0xB79C5305 0xAA500737 |
||||||
|
0x3E07841C 0x7FDEAE5C 0x8E7D44EC 0x5716F2B8 |
||||||
|
0xB03ADA37 0xF0500C0D 0xF01C1F04 0x0200B3FF |
||||||
|
0xAE0CF51A 0x3CB574B2 0x25837A58 0xDC0921BD |
||||||
|
0xD19113F9 0x7CA92FF6 0x94324773 0x22F54701 |
||||||
|
0x3AE5E581 0x37C2DADC 0xC8B57634 0x9AF3DDA7 |
||||||
|
0xA9446146 0x0FD0030E 0xECC8C73E 0xA4751E41 |
||||||
|
0xE238CD99 0x3BEA0E2F 0x3280BBA1 0x183EB331 |
||||||
|
0x4E548B38 0x4F6DB908 0x6F420D03 0xF60A04BF |
||||||
|
0x2CB81290 0x24977C79 0x5679B072 0xBCAF89AF |
||||||
|
0xDE9A771F 0xD9930810 0xB38BAE12 0xDCCF3F2E |
||||||
|
0x5512721F 0x2E6B7124 0x501ADDE6 0x9F84CD87 |
||||||
|
0x7A584718 0x7408DA17 0xBC9F9ABC 0xE94B7D8C |
||||||
|
0xEC7AEC3A 0xDB851DFA 0x63094366 0xC464C3D2 |
||||||
|
0xEF1C1847 0x3215D908 0xDD433B37 0x24C2BA16 |
||||||
|
0x12A14D43 0x2A65C451 0x50940002 0x133AE4DD |
||||||
|
0x71DFF89E 0x10314E55 0x81AC77D6 0x5F11199B |
||||||
|
0x043556F1 0xD7A3C76B 0x3C11183B 0x5924A509 |
||||||
|
0xF28FE6ED 0x97F1FBFA 0x9EBABF2C 0x1E153C6E |
||||||
|
0x86E34570 0xEAE96FB1 0x860E5E0A 0x5A3E2AB3 |
||||||
|
0x771FE71C 0x4E3D06FA 0x2965DCB9 0x99E71D0F |
||||||
|
0x803E89D6 0x5266C825 0x2E4CC978 0x9C10B36A |
||||||
|
0xC6150EBA 0x94E2EA78 0xA5FC3C53 0x1E0A2DF4 |
||||||
|
0xF2F74EA7 0x361D2B3D 0x1939260F 0x19C27960 |
||||||
|
0x5223A708 0xF71312B6 0xEBADFE6E 0xEAC31F66 |
||||||
|
0xE3BC4595 0xA67BC883 0xB17F37D1 0x018CFF28 |
||||||
|
0xC332DDEF 0xBE6C5AA5 0x65582185 0x68AB9802 |
||||||
|
0xEECEA50F 0xDB2F953B 0x2AEF7DAD 0x5B6E2F84 |
||||||
|
0x1521B628 0x29076170 0xECDD4775 0x619F1510 |
||||||
|
0x13CCA830 0xEB61BD96 0x0334FE1E 0xAA0363CF |
||||||
|
0xB5735C90 0x4C70A239 0xD59E9E0B 0xCBAADE14 |
||||||
|
0xEECC86BC 0x60622CA7 0x9CAB5CAB 0xB2F3846E |
||||||
|
0x648B1EAF 0x19BDF0CA 0xA02369B9 0x655ABB50 |
||||||
|
0x40685A32 0x3C2AB4B3 0x319EE9D5 0xC021B8F7 |
||||||
|
0x9B540B19 0x875FA099 0x95F7997E 0x623D7DA8 |
||||||
|
0xF837889A 0x97E32D77 0x11ED935F 0x16681281 |
||||||
|
0x0E358829 0xC7E61FD6 0x96DEDFA1 0x7858BA99 |
||||||
|
0x57F584A5 0x1B227263 0x9B83C3FF 0x1AC24696 |
||||||
|
0xCDB30AEB 0x532E3054 0x8FD948E4 0x6DBC3128 |
||||||
|
0x58EBF2EF 0x34C6FFEA 0xFE28ED61 0xEE7C3C73 |
||||||
|
0x5D4A14D9 0xE864B7E3 0x42105D14 0x203E13E0 |
||||||
|
0x45EEE2B6 0xA3AAABEA 0xDB6C4F15 0xFACB4FD0 |
||||||
|
0xC742F442 0xEF6ABBB5 0x654F3B1D 0x41CD2105 |
||||||
|
0xD81E799E 0x86854DC7 0xE44B476A 0x3D816250 |
||||||
|
0xCF62A1F2 0x5B8D2646 0xFC8883A0 0xC1C7B6A3 |
||||||
|
0x7F1524C3 0x69CB7492 0x47848A0B 0x5692B285 |
||||||
|
0x095BBF00 0xAD19489D 0x1462B174 0x23820E00 |
||||||
|
0x58428D2A 0x0C55F5EA 0x1DADF43E 0x233F7061 |
||||||
|
0x3372F092 0x8D937E41 0xD65FECF1 0x6C223BDB |
||||||
|
0x7CDE3759 0xCBEE7460 0x4085F2A7 0xCE77326E |
||||||
|
0xA6078084 0x19F8509E 0xE8EFD855 0x61D99735 |
||||||
|
0xA969A7AA 0xC50C06C2 0x5A04ABFC 0x800BCADC |
||||||
|
0x9E447A2E 0xC3453484 0xFDD56705 0x0E1E9EC9 |
||||||
|
0xDB73DBD3 0x105588CD 0x675FDA79 0xE3674340 |
||||||
|
0xC5C43465 0x713E38D8 0x3D28F89E 0xF16DFF20 |
||||||
|
0x153E21E7 0x8FB03D4A 0xE6E39F2B 0xDB83ADF7 |
||||||
|
0xE93D5A68 0x948140F7 0xF64C261C 0x94692934 |
||||||
|
0x411520F7 0x7602D4F7 0xBCF46B2E 0xD4A20068 |
||||||
|
0xD4082471 0x3320F46A 0x43B7D4B7 0x500061AF |
||||||
|
0x1E39F62E 0x97244546 0x14214F74 0xBF8B8840 |
||||||
|
0x4D95FC1D 0x96B591AF 0x70F4DDD3 0x66A02F45 |
||||||
|
0xBFBC09EC 0x03BD9785 0x7FAC6DD0 0x31CB8504 |
||||||
|
0x96EB27B3 0x55FD3941 0xDA2547E6 0xABCA0A9A |
||||||
|
0x28507825 0x530429F4 0x0A2C86DA 0xE9B66DFB |
||||||
|
0x68DC1462 0xD7486900 0x680EC0A4 0x27A18DEE |
||||||
|
0x4F3FFEA2 0xE887AD8C 0xB58CE006 0x7AF4D6B6 |
||||||
|
0xAACE1E7C 0xD3375FEC 0xCE78A399 0x406B2A42 |
||||||
|
0x20FE9E35 0xD9F385B9 0xEE39D7AB 0x3B124E8B |
||||||
|
0x1DC9FAF7 0x4B6D1856 0x26A36631 0xEAE397B2 |
||||||
|
0x3A6EFA74 0xDD5B4332 0x6841E7F7 0xCA7820FB |
||||||
|
0xFB0AF54E 0xD8FEB397 0x454056AC 0xBA489527 |
||||||
|
0x55533A3A 0x20838D87 0xFE6BA9B7 0xD096954B |
||||||
|
0x55A867BC 0xA1159A58 0xCCA92963 0x99E1DB33 |
||||||
|
0xA62A4A56 0x3F3125F9 0x5EF47E1C 0x9029317C |
||||||
|
0xFDF8E802 0x04272F70 0x80BB155C 0x05282CE3 |
||||||
|
0x95C11548 0xE4C66D22 0x48C1133F 0xC70F86DC |
||||||
|
0x07F9C9EE 0x41041F0F 0x404779A4 0x5D886E17 |
||||||
|
0x325F51EB 0xD59BC0D1 0xF2BCC18F 0x41113564 |
||||||
|
0x257B7834 0x602A9C60 0xDFF8E8A3 0x1F636C1B |
||||||
|
0x0E12B4C2 0x02E1329E 0xAF664FD1 0xCAD18115 |
||||||
|
0x6B2395E0 0x333E92E1 0x3B240B62 0xEEBEB922 |
||||||
|
0x85B2A20E 0xE6BA0D99 0xDE720C8C 0x2DA2F728 |
||||||
|
0xD0127845 0x95B794FD 0x647D0862 0xE7CCF5F0 |
||||||
|
0x5449A36F 0x877D48FA 0xC39DFD27 0xF33E8D1E |
||||||
|
0x0A476341 0x992EFF74 0x3A6F6EAB 0xF4F8FD37 |
||||||
|
0xA812DC60 0xA1EBDDF8 0x991BE14C 0xDB6E6B0D |
||||||
|
0xC67B5510 0x6D672C37 0x2765D43B 0xDCD0E804 |
||||||
|
0xF1290DC7 0xCC00FFA3 0xB5390F92 0x690FED0B |
||||||
|
0x667B9FFB 0xCEDB7D9C 0xA091CF0B 0xD9155EA3 |
||||||
|
0xBB132F88 0x515BAD24 0x7B9479BF 0x763BD6EB |
||||||
|
0x37392EB3 0xCC115979 0x8026E297 0xF42E312D |
||||||
|
0x6842ADA7 0xC66A2B3B 0x12754CCC 0x782EF11C |
||||||
|
0x6A124237 0xB79251E7 0x06A1BBE6 0x4BFB6350 |
||||||
|
0x1A6B1018 0x11CAEDFA 0x3D25BDD8 0xE2E1C3C9 |
||||||
|
0x44421659 0x0A121386 0xD90CEC6E 0xD5ABEA2A |
||||||
|
0x64AF674E 0xDA86A85F 0xBEBFE988 0x64E4C3FE |
||||||
|
0x9DBC8057 0xF0F7C086 0x60787BF8 0x6003604D |
||||||
|
0xD1FD8346 0xF6381FB0 0x7745AE04 0xD736FCCC |
||||||
|
0x83426B33 0xF01EAB71 0xB0804187 0x3C005E5F |
||||||
|
0x77A057BE 0xBDE8AE24 0x55464299 0xBF582E61 |
||||||
|
0x4E58F48F 0xF2DDFDA2 0xF474EF38 0x8789BDC2 |
||||||
|
0x5366F9C3 0xC8B38E74 0xB475F255 0x46FCD9B9 |
||||||
|
0x7AEB2661 0x8B1DDF84 0x846A0E79 0x915F95E2 |
||||||
|
0x466E598E 0x20B45770 0x8CD55591 0xC902DE4C |
||||||
|
0xB90BACE1 0xBB8205D0 0x11A86248 0x7574A99E |
||||||
|
0xB77F19B6 0xE0A9DC09 0x662D09A1 0xC4324633 |
||||||
|
0xE85A1F02 0x09F0BE8C 0x4A99A025 0x1D6EFE10 |
||||||
|
0x1AB93D1D 0x0BA5A4DF 0xA186F20F 0x2868F169 |
||||||
|
0xDCB7DA83 0x573906FE 0xA1E2CE9B 0x4FCD7F52 |
||||||
|
0x50115E01 0xA70683FA 0xA002B5C4 0x0DE6D027 |
||||||
|
0x9AF88C27 0x773F8641 0xC3604C06 0x61A806B5 |
||||||
|
0xF0177A28 0xC0F586E0 0x006058AA 0x30DC7D62 |
||||||
|
0x11E69ED7 0x2338EA63 0x53C2DD94 0xC2C21634 |
||||||
|
0xBBCBEE56 0x90BCB6DE 0xEBFC7DA1 0xCE591D76 |
||||||
|
0x6F05E409 0x4B7C0188 0x39720A3D 0x7C927C24 |
||||||
|
0x86E3725F 0x724D9DB9 0x1AC15BB4 0xD39EB8FC |
||||||
|
0xED545578 0x08FCA5B5 0xD83D7CD3 0x4DAD0FC4 |
||||||
|
0x1E50EF5E 0xB161E6F8 0xA28514D9 0x6C51133C |
||||||
|
0x6FD5C7E7 0x56E14EC4 0x362ABFCE 0xDDC6C837 |
||||||
|
0xD79A3234 0x92638212 0x670EFA8E 0x406000E0 |
||||||
|
0x3A39CE37 0xD3FAF5CF 0xABC27737 0x5AC52D1B |
||||||
|
0x5CB0679E 0x4FA33742 0xD3822740 0x99BC9BBE |
||||||
|
0xD5118E9D 0xBF0F7315 0xD62D1C7E 0xC700C47B |
||||||
|
0xB78C1B6B 0x21A19045 0xB26EB1BE 0x6A366EB4 |
||||||
|
0x5748AB2F 0xBC946E79 0xC6A376D2 0x6549C2C8 |
||||||
|
0x530FF8EE 0x468DDE7D 0xD5730A1D 0x4CD04DC6 |
||||||
|
0x2939BBDB 0xA9BA4650 0xAC9526E8 0xBE5EE304 |
||||||
|
0xA1FAD5F0 0x6A2D519A 0x63EF8CE2 0x9A86EE22 |
||||||
|
0xC089C2B8 0x43242EF6 0xA51E03AA 0x9CF2D0A4 |
||||||
|
0x83C061BA 0x9BE96A4D 0x8FE51550 0xBA645BD6 |
||||||
|
0x2826A2F9 0xA73A3AE1 0x4BA99586 0xEF5562E9 |
||||||
|
0xC72FEFD3 0xF752F7DA 0x3F046F69 0x77FA0A59 |
||||||
|
0x80E4A915 0x87B08601 0x9B09E6AD 0x3B3EE593 |
||||||
|
0xE990FD5A 0x9E34D797 0x2CF0B7D9 0x022B8B51 |
||||||
|
0x96D5AC3A 0x017DA67D 0xD1CF3ED6 0x7C7D2D28 |
||||||
|
0x1F9F25CF 0xADF2B89B 0x5AD6B472 0x5A88F54C |
||||||
|
0xE029AC71 0xE019A5E6 0x47B0ACFD 0xED93FA9B |
||||||
|
0xE8D3C48D 0x283B57CC 0xF8D56629 0x79132E28 |
||||||
|
0x785F0191 0xED756055 0xF7960E44 0xE3D35E8C |
||||||
|
0x15056DD4 0x88F46DBA 0x03A16125 0x0564F0BD |
||||||
|
0xC3EB9E15 0x3C9057A2 0x97271AEC 0xA93A072A |
||||||
|
0x1B3F6D9B 0x1E6321F5 0xF59C66FB 0x26DCF319 |
||||||
|
0x7533D928 0xB155FDF5 0x03563482 0x8ABA3CBB |
||||||
|
0x28517711 0xC20AD9F8 0xABCC5167 0xCCAD925F |
||||||
|
0x4DE81751 0x3830DC8E 0x379D5862 0x9320F991 |
||||||
|
0xEA7A90C2 0xFB3E7BCE 0x5121CE64 0x774FBE32 |
||||||
|
0xA8B6E37E 0xC3293D46 0x48DE5369 0x6413E680 |
||||||
|
0xA2AE0810 0xDD6DB224 0x69852DFD 0x09072166 |
||||||
|
0xB39A460A 0x6445C0DD 0x586CDECF 0x1C20C8AE |
||||||
|
0x5BBEF7DD 0x1B588D40 0xCCD2017F 0x6BB4E3BB |
||||||
|
0xDDA26A7E 0x3A59FF45 0x3E350A44 0xBCB4CDD5 |
||||||
|
0x72EACEA8 0xFA6484BB 0x8D6612AE 0xBF3C6F47 |
||||||
|
0xD29BE463 0x542F5D9E 0xAEC2771B 0xF64E6370 |
||||||
|
0x740E0D8D 0xE75B1357 0xF8721671 0xAF537D5D |
||||||
|
0x4040CB08 0x4EB4E2CC 0x34D2466A 0x0115AF84 |
||||||
|
0xE1B00428 0x95983A1D 0x06B89FB4 0xCE6EA048 |
||||||
|
0x6F3F3B82 0x3520AB82 0x011A1D4B 0x277227F8 |
||||||
|
0x611560B1 0xE7933FDC 0xBB3A792B 0x344525BD |
||||||
|
0xA08839E1 0x51CE794B 0x2F32C9B7 0xA01FBAC9 |
||||||
|
0xE01CC87E 0xBCC7D1F6 0xCF0111C3 0xA1E8AAC7 |
||||||
|
0x1A908749 0xD44FBD9A 0xD0DADECB 0xD50ADA38 |
||||||
|
0x0339C32A 0xC6913667 0x8DF9317C 0xE0B12B4F |
||||||
|
0xF79E59B7 0x43F5BB3A 0xF2D519FF 0x27D9459C |
||||||
|
0xBF97222C 0x15E6FC2A 0x0F91FC71 0x9B941525 |
||||||
|
0xFAE59361 0xCEB69CEB 0xC2A86459 0x12BAA8D1 |
||||||
|
0xB6C1075E 0xE3056A0C 0x10D25065 0xCB03A442 |
||||||
|
0xE0EC6E0E 0x1698DB3B 0x4C98A0BE 0x3278E964 |
||||||
|
0x9F1F9532 0xE0D392DF 0xD3A0342B 0x8971F21E |
||||||
|
0x1B0A7441 0x4BA3348C 0xC5BE7120 0xC37632D8 |
||||||
|
0xDF359F8D 0x9B992F2E 0xE60B6F47 0x0FE3F11D |
||||||
|
0xE54CDA54 0x1EDAD891 0xCE6279CF 0xCD3E7E6F |
||||||
|
0x1618B166 0xFD2C1D05 0x848FD2C5 0xF6FB2299 |
||||||
|
0xF523F357 0xA6327623 0x93A83531 0x56CCCD02 |
||||||
|
0xACF08162 0x5A75EBB5 0x6E163697 0x88D273CC |
||||||
|
0xDE966292 0x81B949D0 0x4C50901B 0x71C65614 |
||||||
|
0xE6C6C7BD 0x327A140A 0x45E1D006 0xC3F27B9A |
||||||
|
0xC9AA53FD 0x62A80F00 0xBB25BFE2 0x35BDD2F6 |
||||||
|
0x71126905 0xB2040222 0xB6CBCF7C 0xCD769C2B |
||||||
|
0x53113EC0 0x1640E3D3 0x38ABBD60 0x2547ADF0 |
||||||
|
0xBA38209C 0xF746CE76 0x77AFA1C5 0x20756060 |
||||||
|
0x85CBFE4E 0x8AE88DD8 0x7AAAF9B0 0x4CF9AA7E |
||||||
|
0x1948C25C 0x02FB8A8C 0x01C36AE4 0xD6EBE1F9 |
||||||
|
0x90D4F869 0xA65CDEA0 0x3F09252D 0xC208E69F |
||||||
|
0xB74E6132 0xCE77E25B 0x578FDFE3 0x3AC372E6 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::intEncrypt {P S xl xr} { |
||||||
|
for {set i 0} {$i < 16} {incr i} { |
||||||
|
set xl [expr {$xl ^ [lindex $P $i]}] |
||||||
|
|
||||||
|
set S0a [lindex $S [expr { ($xl >> 24) & 0xff}]] |
||||||
|
set S1b [lindex $S [expr {(($xl >> 16) & 0xff) + 256}]] |
||||||
|
set S2c [lindex $S [expr {(($xl >> 8) & 0xff) + 512}]] |
||||||
|
set S3d [lindex $S [expr { ($xl & 0xff) + 768}]] |
||||||
|
set xr [expr {(((($S0a + $S1b) ^ $S2c) + $S3d) & 0xffffffff) ^ $xr}] |
||||||
|
|
||||||
|
set temp $xl ; set xl $xr ; set xr $temp |
||||||
|
} |
||||||
|
|
||||||
|
set temp $xl ; set xl $xr ; set xr $temp |
||||||
|
return [list [expr {$xl ^ [lindex $P 17]}] [expr {$xr ^ [lindex $P 16]}]] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::intDecrypt {P S xl xr} { |
||||||
|
for {set i 17} {$i > 1} {incr i -1} { |
||||||
|
set xl [expr {$xl ^ [lindex $P $i]}] |
||||||
|
|
||||||
|
set S0a [lindex $S [expr { ($xl >> 24) & 0xff}]] |
||||||
|
set S1b [lindex $S [expr {(($xl >> 16) & 0xff) + 256}]] |
||||||
|
set S2c [lindex $S [expr {(($xl >> 8) & 0xff) + 512}]] |
||||||
|
set S3d [lindex $S [expr { ($xl & 0xff) + 768}]] |
||||||
|
set xr [expr {(((($S0a + $S1b) ^ $S2c) + $S3d) & 0xffffffff) ^ $xr}] |
||||||
|
|
||||||
|
set temp $xl ; set xl $xr ; set xr $temp |
||||||
|
} |
||||||
|
|
||||||
|
set temp $xl ; set xl $xr ; set xr $temp |
||||||
|
return [list [expr {$xl ^ [lindex $P 0]}] [expr {$xr ^ [lindex $P 1]}]] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::Init {mode key iv} { |
||||||
|
variable ORIG_S |
||||||
|
variable ORIG_P |
||||||
|
variable uid |
||||||
|
|
||||||
|
set S $ORIG_S |
||||||
|
set P [list] |
||||||
|
|
||||||
|
set kl [string length $key] |
||||||
|
binary scan $key c* kc |
||||||
|
|
||||||
|
set j 0 |
||||||
|
for {set i 0} {$i < 18} {incr i} { |
||||||
|
set data 0 |
||||||
|
for {set k 0} {$k < 4} {incr k} { |
||||||
|
set data [expr {(($data << 8) | ([lindex $kc $j] & 0xff)) & 0xffffffff}] |
||||||
|
if {[incr j] >= $kl} { |
||||||
|
set j 0 |
||||||
|
} |
||||||
|
} |
||||||
|
set OPi [lindex $ORIG_P $i] |
||||||
|
lappend P [expr {$OPi ^ $data}] |
||||||
|
} |
||||||
|
|
||||||
|
set datal 0 |
||||||
|
set datar 0 |
||||||
|
|
||||||
|
for {set i 0} {$i < 18} {incr i} { |
||||||
|
set ed [intEncrypt $P $S $datal $datar] |
||||||
|
set datal [lindex $ed 0] |
||||||
|
set datar [lindex $ed 1] |
||||||
|
set P [lreplace $P $i [incr i] $datal $datar] |
||||||
|
} |
||||||
|
|
||||||
|
for {set i 0} {$i < 4} {incr i} { |
||||||
|
for {set j 0} {$j < 256} {incr j 2} { |
||||||
|
set ed [intEncrypt $P $S $datal $datar] |
||||||
|
set datal [lindex $ed 0] |
||||||
|
set datar [lindex $ed 1] |
||||||
|
set t [expr {$i * 256 + $j}] |
||||||
|
set S [lreplace $S $t [incr t] $datal $datar] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set token [namespace current]::[incr uid] |
||||||
|
variable $token |
||||||
|
upvar #0 $token state |
||||||
|
array set state [list P $P S $S M $mode I $iv] |
||||||
|
return $token |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::Reset {token iv} { |
||||||
|
upvar #0 $token state |
||||||
|
set state(I) $iv |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::Final {token} { |
||||||
|
# PRAGMA: nocheck |
||||||
|
variable $token |
||||||
|
unset $token |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::EncryptBlock {token block} { |
||||||
|
upvar #0 $token state |
||||||
|
if {[binary scan $block II xl xr] != 2} { |
||||||
|
error "block must be 8 bytes" |
||||||
|
} |
||||||
|
set xl [expr {$xl & 0xffffffff}] |
||||||
|
set xr [expr {$xr & 0xffffffff}] |
||||||
|
set d [intEncrypt $state(P) $state(S) $xl $xr] |
||||||
|
return [binary format I2 $d] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::Encrypt {Key data} { |
||||||
|
upvar #0 $Key state |
||||||
|
set P $state(P) |
||||||
|
set S $state(S) |
||||||
|
set cbc_mode [string equal "cbc" $state(M)] |
||||||
|
|
||||||
|
if {[binary scan $state(I) II s0 s1] != 2} { |
||||||
|
return -code error "invalid initialization vector: must be 8 bytes" |
||||||
|
} |
||||||
|
|
||||||
|
set len [string length $data] |
||||||
|
if {($len % 8) != 0} { |
||||||
|
return -code error "invalid block size: blocks must be 8 bytes" |
||||||
|
} |
||||||
|
|
||||||
|
set s0 [expr {$s0 & 0xffffffff}] |
||||||
|
set s1 [expr {$s1 & 0xffffffff}] |
||||||
|
|
||||||
|
set result "" |
||||||
|
for {set i 0} {$i < $len} {incr i 8} { |
||||||
|
if {[binary scan $data @[set i]II xl xr] != 2} { |
||||||
|
return -code error "oops" |
||||||
|
} |
||||||
|
if {$cbc_mode} { |
||||||
|
set xl [expr {($xl & 0xffffffff) ^ $s0}] |
||||||
|
set xr [expr {($xr & 0xffffffff) ^ $s1}] |
||||||
|
} |
||||||
|
set d [intEncrypt $P $S $xl $xr] |
||||||
|
if {$cbc_mode} { |
||||||
|
set s0 [lindex $d 0] |
||||||
|
set s1 [lindex $d 1] |
||||||
|
} |
||||||
|
append result [binary format I2 $d] |
||||||
|
} |
||||||
|
if {$cbc_mode} { |
||||||
|
set state(I) [binary format II $s0 $s1] |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::DecryptBlock {Key block} { |
||||||
|
upvar #0 $Key state |
||||||
|
if {[binary scan $block II xl xr] != 2} { |
||||||
|
return -code error "invalid block size: block must be 8 bytes" |
||||||
|
} |
||||||
|
set xl [expr {$xl & 0xffffffff}] |
||||||
|
set xr [expr {$xr & 0xffffffff}] |
||||||
|
set d [intDecrypt $state(P) $state(S) $xl $xr] |
||||||
|
return [binary format I2 $d] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::Decrypt {token data} { |
||||||
|
upvar #0 $token state |
||||||
|
set P $state(P) |
||||||
|
set S $state(S) |
||||||
|
set cbc_mode [string equal "cbc" $state(M)] |
||||||
|
|
||||||
|
if {[binary scan $state(I) II s0 s1] != 2} { |
||||||
|
return -code error "initialization vector must be 8 bytes" |
||||||
|
} |
||||||
|
|
||||||
|
set len [string length $data] |
||||||
|
if {($len % 8) != 0} { |
||||||
|
return -code error "block size invalid" |
||||||
|
} |
||||||
|
|
||||||
|
set s0 [expr {$s0 & 0xffffffff}] |
||||||
|
set s1 [expr {$s1 & 0xffffffff}] |
||||||
|
|
||||||
|
set result "" |
||||||
|
for {set i 0} {$i < $len} {incr i 8} { |
||||||
|
if {[binary scan $data @[set i]II xl xr] != 2} { |
||||||
|
error "oops" |
||||||
|
} |
||||||
|
set xl [expr {$xl & 0xffffffff}] |
||||||
|
set xr [expr {$xr & 0xffffffff}] |
||||||
|
set d [intDecrypt $P $S $xl $xr] |
||||||
|
if {$cbc_mode} { |
||||||
|
set d0 [lindex $d 0] |
||||||
|
set d1 [lindex $d 1] |
||||||
|
set c0 [expr {$d0 ^ $s0}] |
||||||
|
set c1 [expr {$d1 ^ $s1}] |
||||||
|
set s0 $xl |
||||||
|
set s1 $xr |
||||||
|
append result [binary format II $c0 $c1] |
||||||
|
} else { |
||||||
|
append result [binary format I2 $d] |
||||||
|
} |
||||||
|
} |
||||||
|
if {$cbc_mode} { |
||||||
|
set state(I) [binary format II $s0 $s1] |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# Fileevent handler for chunked file reading. |
||||||
|
# |
||||||
|
proc ::blowfish::Chunk {Key in {out {}} {chunksize 4096} {pad \0}} { |
||||||
|
upvar #0 $Key state |
||||||
|
|
||||||
|
if {[eof $in]} { |
||||||
|
fileevent $in readable {} |
||||||
|
set state(reading) 0 |
||||||
|
set data $state(remainder) |
||||||
|
|
||||||
|
# Only pad at the end of the stream. |
||||||
|
if {[string length $pad] > 0} { |
||||||
|
set data [Pad $data 8 $pad] |
||||||
|
} |
||||||
|
} else { |
||||||
|
set data [read $in $chunksize] |
||||||
|
#puts "Chunk: reading [string len $data] bytes" |
||||||
|
set data $state(remainder)$data |
||||||
|
|
||||||
|
# If data is not a multiple of 8, state(remainder) will hold |
||||||
|
# excess bytes for the next round. |
||||||
|
set pagedlen [expr {([string length $data] / 8) * 8}] |
||||||
|
set state(remainder) [string range $data $pagedlen end] |
||||||
|
incr pagedlen -1 |
||||||
|
set data [string range $data 0 $pagedlen] |
||||||
|
} |
||||||
|
|
||||||
|
if {![string length $data]} return |
||||||
|
|
||||||
|
if {[set code [catch { |
||||||
|
set cipher [$state(cmd) $Key $data] |
||||||
|
} msg]]} { |
||||||
|
fileevent $in readable {} |
||||||
|
set state(reading) 0 |
||||||
|
set state(err) [list $code $msg] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if {$out == {}} { |
||||||
|
append state(output) $cipher |
||||||
|
} else { |
||||||
|
puts -nonewline $out $cipher |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# LoadAccelerator -- |
||||||
|
# |
||||||
|
# This package can make use of a number of compiled extensions to |
||||||
|
# accelerate the digest computation. This procedure manages the |
||||||
|
# use of these extensions within the package. During normal usage |
||||||
|
# this should not be called, but the test package manipulates the |
||||||
|
# list of enabled accelerators. |
||||||
|
# |
||||||
|
proc ::blowfish::LoadAccelerator {name} { |
||||||
|
variable accel |
||||||
|
set r 0 |
||||||
|
switch -exact -- $name { |
||||||
|
trf { |
||||||
|
if {![catch {package require Trfcrypt}]} { |
||||||
|
set block [string repeat \0 8] |
||||||
|
set r [expr {![catch {::blowfish -dir enc -mode ecb -key $block $block} msg]}] |
||||||
|
} |
||||||
|
} |
||||||
|
default { |
||||||
|
return -code error "invalid accelerator package:\ |
||||||
|
must be one of [join [array names accel] {, }]" |
||||||
|
} |
||||||
|
} |
||||||
|
set accel($name) $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
proc ::blowfish::Hex {data} { |
||||||
|
binary scan $data H* r |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::SetOneOf {lst item} { |
||||||
|
set ndx [lsearch -glob $lst "${item}*"] |
||||||
|
if {$ndx == -1} { |
||||||
|
set err [join $lst ", "] |
||||||
|
return -code error "invalid mode \"$item\": must be one of $err" |
||||||
|
} |
||||||
|
return [lindex $lst $ndx] |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::CheckSize {what size thing} { |
||||||
|
if {[string length $thing] != $size} { |
||||||
|
return -code error "invalid value for $what: must be $size bytes long" |
||||||
|
} |
||||||
|
return $thing |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::CheckPad {char} { |
||||||
|
if {[string length $char] > 1} { |
||||||
|
return -code error "invalid value: should be a char or empty string" |
||||||
|
} |
||||||
|
return $char |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::Pad {data blocksize {fill \0}} { |
||||||
|
set len [string length $data] |
||||||
|
if {$len == 0} { |
||||||
|
# do not pad an empty string |
||||||
|
} elseif {($len % $blocksize) != 0} { |
||||||
|
set pad [expr {$blocksize - ($len % $blocksize)}] |
||||||
|
append data [string repeat $fill $pad] |
||||||
|
} |
||||||
|
return $data |
||||||
|
} |
||||||
|
|
||||||
|
# Description: |
||||||
|
# Pop the nth element off a list. Used in options processing. |
||||||
|
# |
||||||
|
proc ::blowfish::Pop {varname {nth 0}} { |
||||||
|
upvar $varname args |
||||||
|
set r [lindex $args $nth] |
||||||
|
set args [lreplace $args $nth $nth] |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
proc ::blowfish::blowfish {args} { |
||||||
|
variable accel |
||||||
|
array set opts {-dir encrypt -mode cbc -key {} -in {} -out {} -hex 0 -pad \0} |
||||||
|
set opts(-chunksize) 4096 |
||||||
|
set opts(-iv) [string repeat \0 8] |
||||||
|
set modes {ecb cbc} |
||||||
|
set dirs {encrypt decrypt} |
||||||
|
while {[string match -* [set option [lindex $args 0]]]} { |
||||||
|
switch -exact -- $option { |
||||||
|
-mode { set opts(-mode) [SetOneOf $modes [Pop args 1]] } |
||||||
|
-dir { set opts(-dir) [SetOneOf $dirs [Pop args 1]] } |
||||||
|
-iv { set opts(-iv) [CheckSize -iv 8 [Pop args 1]] } |
||||||
|
-key { set opts(-key) [Pop args 1] } |
||||||
|
-in { set opts(-in) [Pop args 1] } |
||||||
|
-out { set opts(-out) [Pop args 1] } |
||||||
|
-chunksize { set opts(-chunksize) [Pop args 1] } |
||||||
|
-hex { set opts(-hex) 1 } |
||||||
|
-pad { set opts(-pad) [CheckPad [Pop args 1]] } |
||||||
|
-- { Pop args; break } |
||||||
|
default { |
||||||
|
if {[string length $opts(-in)] == 0 && [llength $args] == 1} break |
||||||
|
set err [join [lsort [array names opts]] ", "] |
||||||
|
return -code error "bad option \"$option\":\ |
||||||
|
must be one of $err" |
||||||
|
} |
||||||
|
} |
||||||
|
Pop args |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-key) == {}} { |
||||||
|
return -code error "no key provided: the -key option is required" |
||||||
|
} |
||||||
|
|
||||||
|
set r {} |
||||||
|
if {$opts(-in) == {}} { |
||||||
|
# Immediate data (plain text is argument). |
||||||
|
|
||||||
|
if {[llength $args] != 1} { |
||||||
|
return -code error "wrong \# args:\ |
||||||
|
should be \"blowfish ?options...? -key keydata plaintext\"" |
||||||
|
} |
||||||
|
|
||||||
|
set data [lindex $args 0] |
||||||
|
if {[string length $opts(-pad)] > 0} { |
||||||
|
set data [Pad [lindex $args 0] 8 $opts(-pad)] |
||||||
|
} |
||||||
|
if {$accel(trf)} { |
||||||
|
set r [::blowfish -dir $opts(-dir) -mode $opts(-mode) \ |
||||||
|
-key $opts(-key) -iv $opts(-iv) -- $data] |
||||||
|
} else { |
||||||
|
set Key [Init $opts(-mode) $opts(-key) $opts(-iv)] |
||||||
|
if {[string equal $opts(-dir) "encrypt"]} { |
||||||
|
set r [Encrypt $Key $data] |
||||||
|
} else { |
||||||
|
set r [Decrypt $Key $data] |
||||||
|
} |
||||||
|
Final $Key |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-out) != {}} { |
||||||
|
puts -nonewline $opts(-out) $r |
||||||
|
set r {} |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
# Channel data (plain text is read from a binary channel). |
||||||
|
|
||||||
|
if {[llength $args] != 0} { |
||||||
|
return -code error "wrong \# args:\ |
||||||
|
should be \"blowfish ?options...? -key keydata -in channel\"" |
||||||
|
} |
||||||
|
|
||||||
|
set Key [Init $opts(-mode) $opts(-key) $opts(-iv)] |
||||||
|
upvar $Key state |
||||||
|
set state(reading) 1 |
||||||
|
if {[string equal $opts(-dir) "encrypt"]} { |
||||||
|
set state(cmd) Encrypt |
||||||
|
} else { |
||||||
|
set state(cmd) Decrypt |
||||||
|
} |
||||||
|
set state(output) "" |
||||||
|
set state(remainder) "" |
||||||
|
fileevent $opts(-in) readable \ |
||||||
|
[list [namespace origin Chunk] \ |
||||||
|
$Key $opts(-in) $opts(-out) $opts(-chunksize) $opts(-pad)] |
||||||
|
if {[info commands ::tkwait] != {}} { |
||||||
|
tkwait variable [subst $Key](reading) |
||||||
|
} else { |
||||||
|
vwait [subst $Key](reading) |
||||||
|
} |
||||||
|
|
||||||
|
if {[info exists state(err)]} { |
||||||
|
foreach {code msg} $state(err) break |
||||||
|
return -code $code $msg |
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-out) == {}} { |
||||||
|
set r $state(output) |
||||||
|
} |
||||||
|
Final $Key |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if {$opts(-hex)} { |
||||||
|
set r [Hex $r] |
||||||
|
} |
||||||
|
return $r |
||||||
|
} |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
|
||||||
|
# Try and load a compiled extension to help. |
||||||
|
namespace eval ::blowfish { |
||||||
|
variable e {} |
||||||
|
foreach e {trf} { |
||||||
|
if {[LoadAccelerator $e]} break |
||||||
|
} |
||||||
|
unset e |
||||||
|
} |
||||||
|
|
||||||
|
package provide blowfish 1.0.6 |
||||||
|
|
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# |
||||||
|
# Local Variables: |
||||||
|
# mode: tcl |
||||||
|
# indent-tabs-mode: nil |
||||||
|
# End: |
@ -0,0 +1,5 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} { |
||||||
|
# PRAGMA: returnok |
||||||
|
return |
||||||
|
} |
||||||
|
package ifneeded blowfish 1.0.6 [list source [file join $dir blowfish.tcl]] |
@ -0,0 +1,185 @@ |
|||||||
|
## -*- tcl -*- |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
|
||||||
|
# Copyright (c) 2008 Andreas Kupries <andreas_kupries@users.sourceforge.net> |
||||||
|
|
||||||
|
# Aynchronous in-memory cache. Queries of the cache generate |
||||||
|
# asynchronous requests for data for unknown parts, with asynchronous |
||||||
|
# result return. Data found in the cache may return fully asynchronous |
||||||
|
# as well, or semi-synchronous. The latter meaning that the regular |
||||||
|
# callbacks are used, but invoked directly, and not decoupled through |
||||||
|
# events. The cache can be pre-filled synchronously. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Requisites |
||||||
|
|
||||||
|
package require Tcl 8.5 9 ; # |
||||||
|
package require snit ; # |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## |
||||||
|
|
||||||
|
snit::type cache::async { |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Unknown methods and options are forwared to the object actually |
||||||
|
## providing the cached data, making the cache a proper facade for |
||||||
|
## it. |
||||||
|
|
||||||
|
delegate method * to myprovider |
||||||
|
delegate option * to myprovider |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## API |
||||||
|
|
||||||
|
option -full-async-results -default 1 -type snit::boolean |
||||||
|
|
||||||
|
constructor {provider args} { |
||||||
|
set myprovider $provider |
||||||
|
$self configurelist $args |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
method get {key donecmd} { |
||||||
|
# Register request |
||||||
|
lappend mywaiting($key) $donecmd |
||||||
|
|
||||||
|
# Check if the request can be satisfied from the cache. If yes |
||||||
|
# then that is done. |
||||||
|
|
||||||
|
if {[info exists mymiss($key)]} { |
||||||
|
$self NotifyUnset 1 $key |
||||||
|
return |
||||||
|
} elseif {[info exists myhit($key)]} { |
||||||
|
$self NotifySet 1 $key |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# We have to ask our provider if there is data or |
||||||
|
# not. however, if a request for this key is already in flight |
||||||
|
# then we have to do nothing more. Our registration at the |
||||||
|
# beginning ensures that we will get notified when the |
||||||
|
# requested information comes back. |
||||||
|
|
||||||
|
if {[llength $mywaiting($key)] > 1} return |
||||||
|
|
||||||
|
# This is the first query for this key, ask the provider. |
||||||
|
|
||||||
|
after idle [linsert $myprovider end get $key $self] |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
method clear {args} { |
||||||
|
# Note: This method cannot interfere with async queries caused |
||||||
|
# by 'get' invokations. If the data is present, and now |
||||||
|
# removed, all 'get' invokations before this call were |
||||||
|
# satisfied from the cache and only invokations coming after |
||||||
|
# it can trigger async queries of the provider. If the data is |
||||||
|
# not present the state will not change, and queries in flight |
||||||
|
# simply refill the cache as they would do anyway without the |
||||||
|
# 'clear'. |
||||||
|
|
||||||
|
if {![llength $args]} { |
||||||
|
array unset myhit * |
||||||
|
array unset mymiss * |
||||||
|
} elseif {[llength $args] == 1} { |
||||||
|
set key [lindex $args 0] |
||||||
|
unset -nocomplain myhit($key) |
||||||
|
unset -nocomplain mymiss($key) |
||||||
|
} else { |
||||||
|
WrongArgs ?key? |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
method exists {key} { |
||||||
|
return [expr {[info exists myhit($key)] || [info exists mymiss($key)]}] |
||||||
|
} |
||||||
|
|
||||||
|
method set {key value} { |
||||||
|
# Add data to the cache, and notify all outstanding queries. |
||||||
|
# Nothing is done if the key is already known and has the same |
||||||
|
# value. |
||||||
|
|
||||||
|
# This is the method invoked by the provider in response to |
||||||
|
# queries, and also the method to use to prefill the cache |
||||||
|
# with data. |
||||||
|
|
||||||
|
if { |
||||||
|
[info exists myhit($key)] && |
||||||
|
($value eq $myhit($key)) |
||||||
|
} return |
||||||
|
|
||||||
|
set myhit($key) $value |
||||||
|
unset -nocomplain mymiss($key) |
||||||
|
$self NotifySet 0 $key |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
method unset {key} { |
||||||
|
# Add hole to the cache, and notify all outstanding queries. |
||||||
|
# This is the method invoked by the provider in response to |
||||||
|
# queries, and also the method to use to prefill the cache |
||||||
|
# with holes. |
||||||
|
unset -nocomplain myhit($key) |
||||||
|
set mymiss($key) . |
||||||
|
$self NotifyUnset 0 $key |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
method NotifySet {found key} { |
||||||
|
if {![info exists mywaiting($key)] || ![llength $mywaiting($key)]} return |
||||||
|
|
||||||
|
set pending $mywaiting($key) |
||||||
|
unset mywaiting($key) |
||||||
|
|
||||||
|
set value $myhit($key) |
||||||
|
if {$found && !$options(-full-async-results)} { |
||||||
|
foreach donecmd $pending { |
||||||
|
uplevel \#0 [linsert $donecmd end set $key $value] |
||||||
|
} |
||||||
|
} else { |
||||||
|
foreach donecmd $pending { |
||||||
|
after idle [linsert $donecmd end set $key $value] |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
method NotifyUnset {found key} { |
||||||
|
if {![info exists mywaiting($key)] || ![llength $mywaiting($key)]} return |
||||||
|
|
||||||
|
set pending $mywaiting($key) |
||||||
|
unset mywaiting($key) |
||||||
|
|
||||||
|
if {$found && !$options(-full-async-results)} { |
||||||
|
foreach donecmd $pending { |
||||||
|
uplevel \#0 [linsert $donecmd end unset $key] |
||||||
|
} |
||||||
|
} else { |
||||||
|
foreach donecmd $pending { |
||||||
|
after idle [linsert $donecmd end unset $key] |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc WrongArgs {expected} { |
||||||
|
return -code error "wrong#args: Expected $expected" |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## State |
||||||
|
|
||||||
|
variable myprovider ; # Command prefix providing the data to cache. |
||||||
|
variable myhit -array {} ; # Cache array mapping keys to values. |
||||||
|
variable mymiss -array {} ; # Cache array mapping keys to holes. |
||||||
|
variable mywaiting -array {} ; # Map of keys pending to notifier commands. |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
} |
||||||
|
|
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## Ready |
||||||
|
|
||||||
|
package provide cache::async 0.3.2 |
@ -0,0 +1,3 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded cache::async 0.3.2 [list source [file join $dir async.tcl]] |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.6 9]} {return} |
||||||
|
package ifneeded clay 0.8.8 [list source [file join $dir clay.tcl]] |
||||||
|
|
@ -0,0 +1,280 @@ |
|||||||
|
## -*- tcl -*- |
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## Copyright (c) 2004 Kevin Kenny |
||||||
|
## Origin http://wiki.tcl.tk/13094 |
||||||
|
## Modified for Tcl 8.5 only (eval -> {*}). |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## Requisites |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package provide clock::iso8601 0.2 |
||||||
|
namespace eval ::clock::iso8601 {} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## API |
||||||
|
|
||||||
|
# iso8601::parse_date -- |
||||||
|
# |
||||||
|
# Parse an ISO8601 date/time string in an unknown variant. |
||||||
|
# |
||||||
|
# Parameters: |
||||||
|
# string -- String to parse |
||||||
|
# args -- Arguments as for [clock scan]; may include any of |
||||||
|
# the '-base', '-gmt', '-locale' or '-timezone options. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns the given date in seconds from the Posix epoch. |
||||||
|
|
||||||
|
proc ::clock::iso8601::parse_date { string args } { |
||||||
|
variable DatePatterns |
||||||
|
variable Repattern |
||||||
|
foreach { regex interpretation } $DatePatterns { |
||||||
|
if { [regexp "^$regex\$" $string] } { |
||||||
|
#puts A|$string|\t|$regex|\t|$interpretation| |
||||||
|
|
||||||
|
# For incomplete dates (month and/or day missing), we have |
||||||
|
# to set our own default values to overcome clock scan's |
||||||
|
# settings. We do this by switching to a different pattern |
||||||
|
# and extending the input properly for that pattern. |
||||||
|
|
||||||
|
if {[dict exists $Repattern $interpretation]} { |
||||||
|
lassign [dict get $Repattern $interpretation] interpretation adjust modifier |
||||||
|
{*}$modifier |
||||||
|
# adjust irrelevant here, see parse_time for use. |
||||||
|
} |
||||||
|
|
||||||
|
#puts B|$string|\t|$regex|\t|$interpretation| |
||||||
|
return [clock scan $string -format $interpretation {*}$args] |
||||||
|
} |
||||||
|
} |
||||||
|
return -code error "not an iso8601 date string" |
||||||
|
} |
||||||
|
|
||||||
|
# iso8601::parse_time -- |
||||||
|
# |
||||||
|
# Parse a point-in-time in ISO8601 format |
||||||
|
# |
||||||
|
# Parameters: |
||||||
|
# string -- String to parse |
||||||
|
# args -- Arguments as for [clock scan]; may include any of |
||||||
|
# the '-base', '-gmt', '-locale' or '-timezone options. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns the given time in seconds from the Posix epoch. |
||||||
|
|
||||||
|
proc ::clock::iso8601::parse_time { string args } { |
||||||
|
variable DatePatterns |
||||||
|
variable Repattern |
||||||
|
if {![MatchTime $string field]} { |
||||||
|
return -code error "not an iso8601 time string" |
||||||
|
} |
||||||
|
|
||||||
|
#parray field |
||||||
|
#puts A|$string| |
||||||
|
|
||||||
|
set pattern {} |
||||||
|
foreach {regex interpretation} $DatePatterns { |
||||||
|
if {[Has $interpretation tstart]} { |
||||||
|
append pattern $interpretation |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {[dict exists $Repattern $pattern]} { |
||||||
|
lassign [dict get $Repattern $pattern] interpretation adjust modifier |
||||||
|
{*}$modifier |
||||||
|
incr tstart $adjust |
||||||
|
} |
||||||
|
|
||||||
|
append pattern [Get T len] |
||||||
|
incr tstart $len |
||||||
|
|
||||||
|
if {[Has %H tstart]} { |
||||||
|
append pattern %H [Get Hcolon len] |
||||||
|
incr tstart $len |
||||||
|
|
||||||
|
if {[Has %M tstart]} { |
||||||
|
append pattern %M [Get Mcolon len] |
||||||
|
incr tstart $len |
||||||
|
|
||||||
|
if {[Has %S tstart]} { |
||||||
|
append pattern %S |
||||||
|
} else { |
||||||
|
# No seconds, default to start of minute. |
||||||
|
append pattern %S |
||||||
|
Insert string $tstart 00 |
||||||
|
} |
||||||
|
} else { |
||||||
|
# No minutes, nor seconds, default to start of hour. |
||||||
|
append pattern %M%S |
||||||
|
Insert string $tstart 0000 |
||||||
|
} |
||||||
|
} else { |
||||||
|
# No time information, default to midnight. |
||||||
|
append pattern %H%M%S |
||||||
|
Insert string $tstart 000000 |
||||||
|
} |
||||||
|
if {[Has %Z _]} { |
||||||
|
append pattern %Z |
||||||
|
} |
||||||
|
|
||||||
|
#puts B|$string|\t|$pattern| |
||||||
|
return [clock scan $string -format $pattern {*}$args] |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
|
||||||
|
proc ::clock::iso8601::Get {x lv} { |
||||||
|
upvar 1 field field string string $lv len |
||||||
|
lassign $field($x) s e |
||||||
|
if {($s >= 0) && ($e >= 0)} { |
||||||
|
set len [expr {$e - $s + 1}] |
||||||
|
return [string range $string $s $e] |
||||||
|
} |
||||||
|
set len 0 |
||||||
|
return "" |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
proc ::clock::iso8601::Has {x nv} { |
||||||
|
upvar 1 field field string string $nv next |
||||||
|
lassign $field($x) s e |
||||||
|
if {($s >= 0) && ($e >= 0)} { |
||||||
|
set next $e |
||||||
|
incr next |
||||||
|
return 1 |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
proc ::clock::iso8601::Insert {sv index str} { |
||||||
|
upvar 1 $sv string |
||||||
|
append r [string range $string 0 ${index}-1] |
||||||
|
append r $str |
||||||
|
append r [string range $string $index end] |
||||||
|
set string $r |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## State |
||||||
|
|
||||||
|
namespace eval ::clock::iso8601 { |
||||||
|
|
||||||
|
namespace export parse_date parse_time |
||||||
|
namespace ensemble create |
||||||
|
|
||||||
|
# Enumerate the patterns that we recognize for an ISO8601 date as both |
||||||
|
# the regexp patterns that match them and the [clock] patterns that scan |
||||||
|
# them. |
||||||
|
|
||||||
|
variable DatePatterns { |
||||||
|
{\d\d\d\d-\d\d-\d\d} {%Y-%m-%d} |
||||||
|
{\d\d\d\d\d\d\d\d} {%Y%m%d} |
||||||
|
{\d\d\d\d-\d\d\d} {%Y-%j} |
||||||
|
{\d\d\d\d\d\d\d} {%Y%j} |
||||||
|
{\d\d-\d\d-\d\d} {%y-%m-%d} |
||||||
|
{\d\d\d\d-\d\d} {%Y-%m} |
||||||
|
{\d\d\d\d\d\d} {%y%m%d} |
||||||
|
{\d\d-\d\d\d} {%y-%j} |
||||||
|
{\d\d\d\d\d} {%y%j} |
||||||
|
{--\d\d-\d\d} {--%m-%d} |
||||||
|
{--\d\d\d\d} {--%m%d} |
||||||
|
{--\d\d\d} {--%j} |
||||||
|
{---\d\d} {---%d} |
||||||
|
{\d\d\d\d-W\d\d-\d} {%G-W%V-%u} |
||||||
|
{\d\d\d\dW\d\d\d} {%GW%V%u} |
||||||
|
{\d\d-W\d\d-\d} {%g-W%V-%u} |
||||||
|
{\d\dW\d\d\d} {%gW%V%u} |
||||||
|
{\d\d\d\d-W\d\d} {%G-W%V} |
||||||
|
{\d\d\d\dW\d\d} {%GW%V} |
||||||
|
{-W\d\d-\d} {-W%V-%u} |
||||||
|
{-W\d\d\d} {-W%V%u} |
||||||
|
{-W-\d} {%u} |
||||||
|
{\d\d\d\d} {%Y} |
||||||
|
} |
||||||
|
|
||||||
|
# Dictionary of the patterns requiring modifications to the input |
||||||
|
# for proper month and/or day defaults. |
||||||
|
variable Repattern { |
||||||
|
%Y-%m {%Y-%m-%d 3 {Insert string 7 -01}} |
||||||
|
%Y {%Y-%m-%d 5 {Insert string 4 -01-01}} |
||||||
|
%G-W%V {%G-W%V-%u 1 {Insert string 8 -1}} |
||||||
|
%GW%V {%GW%V%u 1 {Insert string 6 1}} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## Initialization |
||||||
|
|
||||||
|
apply {{} { |
||||||
|
# MatchTime -- (constructed procedure) |
||||||
|
# |
||||||
|
# Match an ISO8601 date/time string and indicate how it matched. |
||||||
|
# |
||||||
|
# Parameters: |
||||||
|
# string -- String to match. |
||||||
|
# fieldArray -- Name of an array in caller's scope that will receive |
||||||
|
# parsed fields of the time. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns 1 if the time was scanned successfully, 0 otherwise. |
||||||
|
# |
||||||
|
# Side effects: |
||||||
|
# Initializes the field array. The keys that are significant: |
||||||
|
# - Any date pattern in 'DatePatterns' indicates that the |
||||||
|
# corresponding value, if non-empty, contains a date string |
||||||
|
# in the given format. |
||||||
|
# - The patterns T, Hcolon, and Mcolon indicate a literal |
||||||
|
# T preceding the time, a colon following the hour, or |
||||||
|
# a colon following the minute. |
||||||
|
# - %H, %M, %S, and %Z indicate the presence of the |
||||||
|
# corresponding parts of the time. |
||||||
|
|
||||||
|
variable DatePatterns |
||||||
|
|
||||||
|
set cmd {regexp -indices -expanded -nocase -- {PATTERN} $timeString ->} |
||||||
|
set re \(?:\(?: |
||||||
|
set sep {} |
||||||
|
foreach {regex interpretation} $DatePatterns { |
||||||
|
append re $sep \( $regex \) |
||||||
|
append cmd " " [list field($interpretation)] |
||||||
|
set sep | |
||||||
|
} |
||||||
|
append re \) {(T|[[:space:]]+)} \)? |
||||||
|
append cmd { field(T)} |
||||||
|
append re {(\d\d)(?:(:?)(\d\d)(?:(:?)(\d\d)?))?} |
||||||
|
append cmd { field(%H) field(Hcolon) } {field(%M) field(Mcolon) field(%S)} |
||||||
|
append re {[[:space:]]*(Z|[-+]\d\d:?\d\d)?} |
||||||
|
append cmd { field(%Z)} |
||||||
|
set cmd [string map [list {{PATTERN}} [list $re]] \ |
||||||
|
$cmd] |
||||||
|
|
||||||
|
proc MatchTime { timeString fieldArray } " |
||||||
|
upvar 1 \$fieldArray field |
||||||
|
$cmd |
||||||
|
" |
||||||
|
|
||||||
|
#puts [info body MatchTime] |
||||||
|
|
||||||
|
} ::clock::iso8601} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
|
||||||
|
return |
||||||
|
# Usage examples, disabled. |
||||||
|
|
||||||
|
if { [info exists ::argv0] && ( $::argv0 eq [info script] ) } { |
||||||
|
puts "::clock::iso8601::parse_date" |
||||||
|
puts [::clock::iso8601::parse_date 1970-01-02 -timezone :UTC] |
||||||
|
puts [::clock::iso8601::parse_date 1970-W01-5 -timezone :UTC] |
||||||
|
puts [time {::clock::iso8601::parse_date 1970-01-02 -timezone :UTC} 1000] |
||||||
|
puts [time {::clock::iso8601::parse_date 1970-W01-5 -timezone :UTC} 1000] |
||||||
|
puts "::clock::iso8601::parse_time" |
||||||
|
puts [clock format [::clock::iso8601::parse_time 2004-W33-2T18:52:24Z] \ |
||||||
|
-format {%X %x %z} -locale system] |
||||||
|
puts [clock format [::clock::iso8601::parse_time 18:52:24Z] \ |
||||||
|
-format {%X %x %z} -locale system] |
||||||
|
puts [time {::clock::iso8601::parse_time 2004-W33-2T18:52:24Z} 1000] |
||||||
|
puts [time {::clock::iso8601::parse_time 18:52:24Z} 1000] |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded clock::rfc2822 0.2 [list source [file join $dir rfc2822.tcl]] |
||||||
|
package ifneeded clock::iso8601 0.2 [list source [file join $dir iso8601.tcl]] |
@ -0,0 +1,214 @@ |
|||||||
|
## -*- tcl -*- |
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## Copyright (c) 2004 Kevin Kenny |
||||||
|
## Origin http://wiki.tcl.tk/24074 |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## Requisites |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package provide clock::rfc2822 0.2 |
||||||
|
namespace eval ::clock::rfc2822 {} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## API |
||||||
|
|
||||||
|
# ::clock::rfc2822::parse_date -- |
||||||
|
# |
||||||
|
# Parses a date expressed in RFC2822 format |
||||||
|
# |
||||||
|
# Parameters: |
||||||
|
# date - The date to parse |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns the date expressed in seconds from the Epoch, or throws |
||||||
|
# an error if the date could not be parsed. |
||||||
|
|
||||||
|
proc ::clock::rfc2822::parse_date { date } { |
||||||
|
variable datepats |
||||||
|
|
||||||
|
# Strip comments and excess whitespace from the date field |
||||||
|
|
||||||
|
regsub -all -expanded { |
||||||
|
\( # open parenthesis |
||||||
|
(:? |
||||||
|
[^()[.\.]] # character other than ()\ |
||||||
|
|\\. # or backslash escape |
||||||
|
)* # any number of times |
||||||
|
\) # close paren |
||||||
|
} $date {} date |
||||||
|
set date [string trim $date] |
||||||
|
|
||||||
|
# Match the patterns in order of preference, returning the first success |
||||||
|
|
||||||
|
foreach {regexp pat} $datepats { |
||||||
|
if { [regexp -nocase $regexp $date] } { |
||||||
|
return [clock scan $date -format $pat] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return -code error -errorcode {CLOCK RFC2822 BADDATE} \ |
||||||
|
"expected an RFC2822 date, got \"$date\"" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## Internals, transient, removed after initialization. |
||||||
|
|
||||||
|
# AddDatePat -- |
||||||
|
# |
||||||
|
# Internal procedure that adds a date pattern to the pattern list |
||||||
|
# |
||||||
|
# Parameters: |
||||||
|
# wpat - Regexp pattern that matches the weekday |
||||||
|
# wgrp - Format group that matches the weekday |
||||||
|
# ypat - Regexp pattern that matches the year |
||||||
|
# ygrp - Format group that matches the year |
||||||
|
# mdpat - Regexp pattern that matches month and day |
||||||
|
# mdgrp - Format group that matches month and day |
||||||
|
# spat - Regexp pattern that matches the seconds of the minute |
||||||
|
# sgrp - Format group that matches the seconds of the minute |
||||||
|
# zpat - Regexp pattern that matches the time zone |
||||||
|
# zgrp - Format group that matches the time zone |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None |
||||||
|
# |
||||||
|
# Side effects: |
||||||
|
# Adds a complete regexp and a complete [clock scan] pattern to |
||||||
|
# 'datepats' |
||||||
|
|
||||||
|
proc ::clock::rfc2822::AddDatePat { wpat wgrp ypat ygrp mdpat mdgrp |
||||||
|
spat sgrp zpat zgrp } { |
||||||
|
variable datepats |
||||||
|
|
||||||
|
set regexp {^[[:space:]]*} |
||||||
|
set pat {} |
||||||
|
append regexp $wpat $mdpat {[[:space:]]+} $ypat |
||||||
|
append pat $wgrp $mdgrp $ygrp |
||||||
|
append regexp {[[:space:]]+\d\d?:\d\d} $spat |
||||||
|
append pat { %H:%M} $sgrp |
||||||
|
append regexp $zpat |
||||||
|
append pat $zgrp |
||||||
|
append regexp {[[:space:]]*$} |
||||||
|
lappend datepats $regexp $pat |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# InitDatePats -- |
||||||
|
# |
||||||
|
# Internal procedure that initializes the set of date patterns |
||||||
|
# allowed in an RFC2822 date |
||||||
|
# |
||||||
|
# Parameters: |
||||||
|
# permissible - 1 if erroneous (but common) time zones are to be |
||||||
|
# allowed, 0 if they are to be rejected |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# None. |
||||||
|
# |
||||||
|
# Side effects: |
||||||
|
|
||||||
|
proc ::clock::rfc2822::InitDatePats { permissible } { |
||||||
|
# Produce formats for the observed variants of RFC 2822 dates. |
||||||
|
# Permissible variants come first in the list; impermissible ones |
||||||
|
# come later. |
||||||
|
|
||||||
|
# The month and day may be "%b %d" or "%d %b" |
||||||
|
|
||||||
|
foreach mdpat {{[[:alpha:]]+[[:space:]]+\d\d?} |
||||||
|
{\d\d?[[:space:]]+[[:alpha:]]+}} \ |
||||||
|
mdgrp {{%b %d} {%d %b}} \ |
||||||
|
mdperm {0 1} { |
||||||
|
# The year may be two digits, or four. Four digit year is |
||||||
|
# done first. |
||||||
|
|
||||||
|
foreach ypat {{\d\d\d\d} {\d\d}} ygrp {%Y %y} { |
||||||
|
# The seconds of the minute may be provided, or |
||||||
|
# omitted. |
||||||
|
|
||||||
|
foreach spat {{:\d\d} {}} sgrp {:%S {}} { |
||||||
|
# The weekday may be provided or omitted. It is |
||||||
|
# common but impermissible to omit the comma after |
||||||
|
# the weekday name. |
||||||
|
|
||||||
|
foreach wpat { |
||||||
|
{(?:Mon|T(?:ue|hu)|Wed|Fri|S(?:at|un)),[[:space:]]+} |
||||||
|
{(?:Mon|T(?:ue|hu)|Wed|Fri|S(?:at|un))[[:space:]]+} |
||||||
|
{} |
||||||
|
} wgrp { |
||||||
|
{%a, } |
||||||
|
{%a } |
||||||
|
{} |
||||||
|
} wperm { |
||||||
|
1 |
||||||
|
0 |
||||||
|
1 |
||||||
|
} { |
||||||
|
# Time zone is defined as +/- hhmm, or as a |
||||||
|
# named time zone. Other common but buggy |
||||||
|
# formats are GMT+-hh:mm, a time zone name in |
||||||
|
# quotation marks, and complete omission of |
||||||
|
# the time zone. |
||||||
|
|
||||||
|
foreach zpat { |
||||||
|
{[[:space:]]+(?:[-+]\d\d\d\d|[[:alpha:]]+)} |
||||||
|
{[[:space:]]+GMT[-+]\d\d:?\d\d} |
||||||
|
{[[:space:]]+"[[:alpha:]]+"} |
||||||
|
{} |
||||||
|
} zgrp { |
||||||
|
{ %Z} |
||||||
|
{ GMT%Z} |
||||||
|
{ "%Z"} |
||||||
|
{} |
||||||
|
} zperm { |
||||||
|
1 |
||||||
|
0 |
||||||
|
0 |
||||||
|
0 |
||||||
|
} { |
||||||
|
if { ($zperm && $wperm && $mdperm) |
||||||
|
== $permissible } { |
||||||
|
AddDatePat $wpat $wgrp $ypat $ygrp \ |
||||||
|
$mdpat $mdgrp \ |
||||||
|
$spat $sgrp $zpat $zgrp |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
## State |
||||||
|
|
||||||
|
namespace eval ::clock::rfc2822 { |
||||||
|
namespace export parse_date |
||||||
|
namespace ensemble create |
||||||
|
|
||||||
|
variable datepats {} |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
# Initialize the date patterns |
||||||
|
|
||||||
|
namespace eval ::clock::rfc2822 { |
||||||
|
InitDatePats 1 |
||||||
|
InitDatePats 0 |
||||||
|
rename AddDatePat {} |
||||||
|
rename InitDatePats {} |
||||||
|
#puts [join $datepats \n] |
||||||
|
} |
||||||
|
|
||||||
|
# # ## ### ##### ######## ############# ##################### |
||||||
|
|
||||||
|
return |
||||||
|
# Usage example, disabled |
||||||
|
|
||||||
|
if {![info exists ::argv0] || [info script] ne $::argv0} return |
||||||
|
puts [clock format \ |
||||||
|
[::clock::rfc2822::parse_date {Mon(day), 23 Aug(ust) 2004 01:23:45 UT}]] |
||||||
|
puts [clock format \ |
||||||
|
[::clock::rfc2822::parse_date "Tue, Jul 21 2009 19:37:47 GMT-0400"]] |
@ -0,0 +1,933 @@ |
|||||||
|
# cmdline.tcl -- |
||||||
|
# |
||||||
|
# This package provides a utility for parsing command line |
||||||
|
# arguments that are processed by our various applications. |
||||||
|
# It also includes a utility routine to determine the |
||||||
|
# application name for use in command line errors. |
||||||
|
# |
||||||
|
# Copyright (c) 1998-2000 by Ajuba Solutions. |
||||||
|
# Copyright (c) 2001-2015 by Andreas Kupries <andreas_kupries@users.sf.net>. |
||||||
|
# Copyright (c) 2003 by David N. Welton <davidw@dedasys.com> |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
|
||||||
|
package require Tcl 8.5 9 |
||||||
|
package provide cmdline 1.5.3 |
||||||
|
|
||||||
|
namespace eval ::cmdline { |
||||||
|
namespace export getArgv0 getopt getKnownOpt getfiles getoptions \ |
||||||
|
getKnownOptions usage |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::getopt -- |
||||||
|
# |
||||||
|
# The cmdline::getopt works in a fashion like the standard |
||||||
|
# C based getopt function. Given an option string and a |
||||||
|
# pointer to an array or args this command will process the |
||||||
|
# first argument and return info on how to proceed. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# argvVar Name of the argv list that you |
||||||
|
# want to process. If options are found the |
||||||
|
# arg list is modified and the processed arguments |
||||||
|
# are removed from the start of the list. |
||||||
|
# optstring A list of command options that the application |
||||||
|
# will accept. If the option ends in ".arg" the |
||||||
|
# getopt routine will use the next argument as |
||||||
|
# an argument to the option. Otherwise the option |
||||||
|
# is a boolean that is set to 1 if present. |
||||||
|
# optVar The variable pointed to by optVar |
||||||
|
# contains the option that was found (without the |
||||||
|
# leading '-' and without the .arg extension). |
||||||
|
# valVar Upon success, the variable pointed to by valVar |
||||||
|
# contains the value for the specified option. |
||||||
|
# This value comes from the command line for .arg |
||||||
|
# options, otherwise the value is 1. |
||||||
|
# If getopt fails, the valVar is filled with an |
||||||
|
# error message. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The getopt function returns 1 if an option was found, 0 if no more |
||||||
|
# options were found, and -1 if an error occurred. |
||||||
|
|
||||||
|
proc ::cmdline::getopt {argvVar optstring optVar valVar} { |
||||||
|
upvar 1 $argvVar argsList |
||||||
|
upvar 1 $optVar option |
||||||
|
upvar 1 $valVar value |
||||||
|
|
||||||
|
set result [getKnownOpt argsList $optstring option value] |
||||||
|
|
||||||
|
if {$result < 0} { |
||||||
|
# Collapse unknown-option error into any-other-error result. |
||||||
|
set result -1 |
||||||
|
} |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::getKnownOpt -- |
||||||
|
# |
||||||
|
# The cmdline::getKnownOpt works in a fashion like the standard |
||||||
|
# C based getopt function. Given an option string and a |
||||||
|
# pointer to an array or args this command will process the |
||||||
|
# first argument and return info on how to proceed. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# argvVar Name of the argv list that you |
||||||
|
# want to process. If options are found the |
||||||
|
# arg list is modified and the processed arguments |
||||||
|
# are removed from the start of the list. Note that |
||||||
|
# unknown options and the args that follow them are |
||||||
|
# left in this list. |
||||||
|
# optstring A list of command options that the application |
||||||
|
# will accept. If the option ends in ".arg" the |
||||||
|
# getopt routine will use the next argument as |
||||||
|
# an argument to the option. Otherwise the option |
||||||
|
# is a boolean that is set to 1 if present. |
||||||
|
# optVar The variable pointed to by optVar |
||||||
|
# contains the option that was found (without the |
||||||
|
# leading '-' and without the .arg extension). |
||||||
|
# valVar Upon success, the variable pointed to by valVar |
||||||
|
# contains the value for the specified option. |
||||||
|
# This value comes from the command line for .arg |
||||||
|
# options, otherwise the value is 1. |
||||||
|
# If getopt fails, the valVar is filled with an |
||||||
|
# error message. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The getKnownOpt function returns 1 if an option was found, |
||||||
|
# 0 if no more options were found, -1 if an unknown option was |
||||||
|
# encountered, and -2 if any other error occurred. |
||||||
|
|
||||||
|
proc ::cmdline::getKnownOpt {argvVar optstring optVar valVar} { |
||||||
|
upvar 1 $argvVar argsList |
||||||
|
upvar 1 $optVar option |
||||||
|
upvar 1 $valVar value |
||||||
|
|
||||||
|
# default settings for a normal return |
||||||
|
set value "" |
||||||
|
set option "" |
||||||
|
set result 0 |
||||||
|
|
||||||
|
# check if we're past the end of the args list |
||||||
|
if {[llength $argsList] != 0} { |
||||||
|
|
||||||
|
# if we got -- or an option that doesn't begin with -, return (skipping |
||||||
|
# the --). otherwise process the option arg. |
||||||
|
switch -glob -- [set arg [lindex $argsList 0]] { |
||||||
|
"--" { |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
} |
||||||
|
"--*" - |
||||||
|
"-*" { |
||||||
|
set option [string range $arg 1 end] |
||||||
|
if {[string equal [string range $option 0 0] "-"]} { |
||||||
|
set option [string range $arg 2 end] |
||||||
|
} |
||||||
|
|
||||||
|
# support for format: [-]-option=value |
||||||
|
set idx [string first "=" $option 1] |
||||||
|
if {$idx != -1} { |
||||||
|
set _val [string range $option [expr {$idx+1}] end] |
||||||
|
set option [string range $option 0 [expr {$idx-1}]] |
||||||
|
} |
||||||
|
|
||||||
|
if {[lsearch -exact $optstring $option] != -1} { |
||||||
|
# Booleans are set to 1 when present |
||||||
|
set value 1 |
||||||
|
set result 1 |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
} elseif {[lsearch -exact $optstring "$option.arg"] != -1} { |
||||||
|
set result 1 |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
|
||||||
|
if {[info exists _val]} { |
||||||
|
set value $_val |
||||||
|
} elseif {[llength $argsList]} { |
||||||
|
set value [lindex $argsList 0] |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
} else { |
||||||
|
set value "Option \"$option\" requires an argument" |
||||||
|
set result -2 |
||||||
|
} |
||||||
|
} else { |
||||||
|
# Unknown option. |
||||||
|
set value "Illegal option \"-$option\"" |
||||||
|
set result -1 |
||||||
|
} |
||||||
|
} |
||||||
|
default { |
||||||
|
# Skip ahead |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::getoptions -- |
||||||
|
# |
||||||
|
# Process a set of command line options, filling in defaults |
||||||
|
# for those not specified. This also generates an error message |
||||||
|
# that lists the allowed flags if an incorrect flag is specified. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# argvVar The name of the argument list, typically argv. |
||||||
|
# We remove all known options and their args from it. |
||||||
|
# In other words, after the call to this command the |
||||||
|
# referenced variable contains only the non-options, |
||||||
|
# and unknown options. |
||||||
|
# optlist A list-of-lists where each element specifies an option |
||||||
|
# in the form: |
||||||
|
# (where flag takes no argument) |
||||||
|
# flag comment |
||||||
|
# |
||||||
|
# (or where flag takes an argument) |
||||||
|
# flag default comment |
||||||
|
# |
||||||
|
# If flag ends in ".arg" then the value is taken from the |
||||||
|
# command line. Otherwise it is a boolean and appears in |
||||||
|
# the result if present on the command line. If flag ends |
||||||
|
# in ".secret", it will not be displayed in the usage. |
||||||
|
# usage Text to include in the usage display. Defaults to |
||||||
|
# "options:" |
||||||
|
# |
||||||
|
# Results |
||||||
|
# Name value pairs suitable for using with array set. |
||||||
|
# A modified `argvVar`. |
||||||
|
|
||||||
|
proc ::cmdline::getoptions {argvVar optlist {usage options:}} { |
||||||
|
upvar 1 $argvVar argv |
||||||
|
|
||||||
|
set opts [GetOptionDefaults $optlist result] |
||||||
|
|
||||||
|
set argc [llength $argv] |
||||||
|
while {[set err [getopt argv $opts opt arg]]} { |
||||||
|
if {$err < 0} { |
||||||
|
set result(?) "" |
||||||
|
break |
||||||
|
} |
||||||
|
set result($opt) $arg |
||||||
|
} |
||||||
|
if {[info exist result(?)] || [info exists result(help)]} { |
||||||
|
Error [usage $optlist $usage] USAGE |
||||||
|
} |
||||||
|
return [array get result] |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::getKnownOptions -- |
||||||
|
# |
||||||
|
# Process a set of command line options, filling in defaults |
||||||
|
# for those not specified. This ignores unknown flags, but generates |
||||||
|
# an error message that lists the correct usage if a known option |
||||||
|
# is used incorrectly. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# argvVar The name of the argument list, typically argv. This |
||||||
|
# We remove all known options and their args from it. |
||||||
|
# In other words, after the call to this command the |
||||||
|
# referenced variable contains only the non-options, |
||||||
|
# and unknown options. |
||||||
|
# optlist A list-of-lists where each element specifies an option |
||||||
|
# in the form: |
||||||
|
# flag default comment |
||||||
|
# If flag ends in ".arg" then the value is taken from the |
||||||
|
# command line. Otherwise it is a boolean and appears in |
||||||
|
# the result if present on the command line. If flag ends |
||||||
|
# in ".secret", it will not be displayed in the usage. |
||||||
|
# usage Text to include in the usage display. Defaults to |
||||||
|
# "options:" |
||||||
|
# |
||||||
|
# Results |
||||||
|
# Name value pairs suitable for using with array set. |
||||||
|
# A modified `argvVar`. |
||||||
|
|
||||||
|
proc ::cmdline::getKnownOptions {argvVar optlist {usage options:}} { |
||||||
|
upvar 1 $argvVar argv |
||||||
|
|
||||||
|
set opts [GetOptionDefaults $optlist result] |
||||||
|
|
||||||
|
# As we encounter them, keep the unknown options and their |
||||||
|
# arguments in this list. Before we return from this procedure, |
||||||
|
# we'll prepend these args to the argList so that the application |
||||||
|
# doesn't lose them. |
||||||
|
|
||||||
|
set unknownOptions [list] |
||||||
|
|
||||||
|
set argc [llength $argv] |
||||||
|
while {[set err [getKnownOpt argv $opts opt arg]]} { |
||||||
|
if {$err == -1} { |
||||||
|
# Unknown option. |
||||||
|
|
||||||
|
# Skip over any non-option items that follow it. |
||||||
|
# For now, add them to the list of unknownOptions. |
||||||
|
lappend unknownOptions [lindex $argv 0] |
||||||
|
set argv [lrange $argv 1 end] |
||||||
|
while {([llength $argv] != 0) \ |
||||||
|
&& ![string match "-*" [lindex $argv 0]]} { |
||||||
|
lappend unknownOptions [lindex $argv 0] |
||||||
|
set argv [lrange $argv 1 end] |
||||||
|
} |
||||||
|
} elseif {$err == -2} { |
||||||
|
set result(?) "" |
||||||
|
break |
||||||
|
} else { |
||||||
|
set result($opt) $arg |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Before returning, prepend the any unknown args back onto the |
||||||
|
# argList so that the application doesn't lose them. |
||||||
|
set argv [concat $unknownOptions $argv] |
||||||
|
|
||||||
|
if {[info exist result(?)] || [info exists result(help)]} { |
||||||
|
Error [usage $optlist $usage] USAGE |
||||||
|
} |
||||||
|
return [array get result] |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::GetOptionDefaults -- |
||||||
|
# |
||||||
|
# This internal procedure processes the option list (that was passed to |
||||||
|
# the getopt or getKnownOpt procedure). The defaultArray gets an index |
||||||
|
# for each option in the option list, the value of which is the option's |
||||||
|
# default value. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# optlist A list-of-lists where each element specifies an option |
||||||
|
# in the form: |
||||||
|
# flag default comment |
||||||
|
# If flag ends in ".arg" then the value is taken from the |
||||||
|
# command line. Otherwise it is a boolean and appears in |
||||||
|
# the result if present on the command line. If flag ends |
||||||
|
# in ".secret", it will not be displayed in the usage. |
||||||
|
# defaultArrayVar The name of the array in which to put argument defaults. |
||||||
|
# |
||||||
|
# Results |
||||||
|
# Name value pairs suitable for using with array set. |
||||||
|
|
||||||
|
proc ::cmdline::GetOptionDefaults {optlist defaultArrayVar} { |
||||||
|
upvar 1 $defaultArrayVar result |
||||||
|
|
||||||
|
set opts {? help} |
||||||
|
foreach opt $optlist { |
||||||
|
set name [lindex $opt 0] |
||||||
|
if {[regsub -- {\.secret$} $name {} name] == 1} { |
||||||
|
# Need to hide this from the usage display and getopt |
||||||
|
} |
||||||
|
lappend opts $name |
||||||
|
if {[regsub -- {\.arg$} $name {} name] == 1} { |
||||||
|
|
||||||
|
# Set defaults for those that take values. |
||||||
|
|
||||||
|
set default [lindex $opt 1] |
||||||
|
set result($name) $default |
||||||
|
} else { |
||||||
|
# The default for booleans is false |
||||||
|
set result($name) 0 |
||||||
|
} |
||||||
|
} |
||||||
|
return $opts |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::usage -- |
||||||
|
# |
||||||
|
# Generate an error message that lists the allowed flags. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# optlist As for cmdline::getoptions |
||||||
|
# usage Text to include in the usage display. Defaults to |
||||||
|
# "options:" |
||||||
|
# |
||||||
|
# Results |
||||||
|
# A formatted usage message |
||||||
|
|
||||||
|
proc ::cmdline::usage {optlist {usage {options:}}} { |
||||||
|
set str "[getArgv0] $usage\n" |
||||||
|
set longest 20 |
||||||
|
set lines {} |
||||||
|
foreach opt [concat $optlist \ |
||||||
|
{{- "Forcibly stop option processing"} {help "Print this message"} {? "Print this message"}}] { |
||||||
|
set name "-[lindex $opt 0]" |
||||||
|
if {[regsub -- {\.secret$} $name {} name] == 1} { |
||||||
|
# Hidden option |
||||||
|
continue |
||||||
|
} |
||||||
|
if {[regsub -- {\.arg$} $name {} name] == 1} { |
||||||
|
append name " value" |
||||||
|
set desc "[lindex $opt 2] <[lindex $opt 1]>" |
||||||
|
} else { |
||||||
|
set desc "[lindex $opt 1]" |
||||||
|
} |
||||||
|
set n [string length $name] |
||||||
|
if {$n > $longest} { set longest $n } |
||||||
|
# max not available before 8.5 - set longest [expr {max($longest, )}] |
||||||
|
lappend lines $name $desc |
||||||
|
} |
||||||
|
foreach {name desc} $lines { |
||||||
|
append str "[string trimright [format " %-*s %s" $longest $name $desc]]\n" |
||||||
|
} |
||||||
|
|
||||||
|
return $str |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::getfiles -- |
||||||
|
# |
||||||
|
# Given a list of file arguments from the command line, compute |
||||||
|
# the set of valid files. On windows, file globbing is performed |
||||||
|
# on each argument. On Unix, only file existence is tested. If |
||||||
|
# a file argument produces no valid files, a warning is optionally |
||||||
|
# generated. |
||||||
|
# |
||||||
|
# This code also uses the full path for each file. If not |
||||||
|
# given it prepends [pwd] to the filename. This ensures that |
||||||
|
# these files will never conflict with files in our zip file. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# patterns The file patterns specified by the user. |
||||||
|
# quiet If this flag is set, no warnings will be generated. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Returns the list of files that match the input patterns. |
||||||
|
|
||||||
|
proc ::cmdline::getfiles {patterns quiet} { |
||||||
|
set result {} |
||||||
|
if {$::tcl_platform(platform) == "windows"} { |
||||||
|
foreach pattern $patterns { |
||||||
|
set pat [file join $pattern] |
||||||
|
set files [glob -nocomplain -- $pat] |
||||||
|
if {$files == {}} { |
||||||
|
if {! $quiet} { |
||||||
|
puts stdout "warning: no files match \"$pattern\"" |
||||||
|
} |
||||||
|
} else { |
||||||
|
foreach file $files { |
||||||
|
lappend result $file |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
set result $patterns |
||||||
|
} |
||||||
|
set files {} |
||||||
|
foreach file $result { |
||||||
|
# Make file an absolute path so that we will never conflict |
||||||
|
# with files that might be contained in our zip file. |
||||||
|
set fullPath [file join [pwd] $file] |
||||||
|
|
||||||
|
if {[file isfile $fullPath]} { |
||||||
|
lappend files $fullPath |
||||||
|
} elseif {! $quiet} { |
||||||
|
puts stdout "warning: no files match \"$file\"" |
||||||
|
} |
||||||
|
} |
||||||
|
return $files |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::getArgv0 -- |
||||||
|
# |
||||||
|
# This command returns the "sanitized" version of argv0. It will strip |
||||||
|
# off the leading path and remove the ".bin" extensions that our apps |
||||||
|
# use because they must be wrapped by a shell script. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# None. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The application name that can be used in error messages. |
||||||
|
|
||||||
|
proc ::cmdline::getArgv0 {} { |
||||||
|
global argv0 |
||||||
|
|
||||||
|
set name [file tail $argv0] |
||||||
|
return [file rootname $name] |
||||||
|
} |
||||||
|
|
||||||
|
## |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## |
||||||
|
# Now the typed versions of the above commands. |
||||||
|
## |
||||||
|
# ### ### ### ######### ######### ######### |
||||||
|
## |
||||||
|
|
||||||
|
# typedCmdline.tcl -- |
||||||
|
# |
||||||
|
# This package provides a utility for parsing typed command |
||||||
|
# line arguments that may be processed by various applications. |
||||||
|
# |
||||||
|
# Copyright (c) 2000 by Ross Palmer Mohn. |
||||||
|
# See the file "license.terms" for information on usage and redistribution |
||||||
|
# of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: cmdline.tcl,v 1.28 2011/02/23 17:41:52 andreas_kupries Exp $ |
||||||
|
|
||||||
|
namespace eval ::cmdline { |
||||||
|
namespace export typedGetopt typedGetoptions typedUsage |
||||||
|
|
||||||
|
# variable cmdline::charclasses -- |
||||||
|
# |
||||||
|
# Create regexp list of allowable character classes |
||||||
|
# from "string is" error message. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# String of character class names separated by "|" characters. |
||||||
|
|
||||||
|
variable charclasses |
||||||
|
#checker exclude badKey |
||||||
|
catch {string is . .} charclasses |
||||||
|
variable dummy |
||||||
|
regexp -- {must be (.+)$} $charclasses dummy charclasses |
||||||
|
regsub -all -- {, (or )?} $charclasses {|} charclasses |
||||||
|
unset dummy |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::typedGetopt -- |
||||||
|
# |
||||||
|
# The cmdline::typedGetopt works in a fashion like the standard |
||||||
|
# C based getopt function. Given an option string and a |
||||||
|
# pointer to a list of args this command will process the |
||||||
|
# first argument and return info on how to proceed. In addition, |
||||||
|
# you may specify a type for the argument to each option. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# argvVar Name of the argv list that you want to process. |
||||||
|
# If options are found, the arg list is modified |
||||||
|
# and the processed arguments are removed from the |
||||||
|
# start of the list. |
||||||
|
# |
||||||
|
# optstring A list of command options that the application |
||||||
|
# will accept. If the option ends in ".xxx", where |
||||||
|
# xxx is any valid character class to the tcl |
||||||
|
# command "string is", then typedGetopt routine will |
||||||
|
# use the next argument as a typed argument to the |
||||||
|
# option. The argument must match the specified |
||||||
|
# character classes (e.g. integer, double, boolean, |
||||||
|
# xdigit, etc.). Alternatively, you may specify |
||||||
|
# ".arg" for an untyped argument. |
||||||
|
# |
||||||
|
# optVar Upon success, the variable pointed to by optVar |
||||||
|
# contains the option that was found (without the |
||||||
|
# leading '-' and without the .xxx extension). If |
||||||
|
# typedGetopt fails the variable is set to the empty |
||||||
|
# string. SOMETIMES! Different for each -value! |
||||||
|
# |
||||||
|
# argVar Upon success, the variable pointed to by argVar |
||||||
|
# contains the argument for the specified option. |
||||||
|
# If typedGetopt fails, the variable is filled with |
||||||
|
# an error message. |
||||||
|
# |
||||||
|
# Argument type syntax: |
||||||
|
# Option that takes no argument. |
||||||
|
# foo |
||||||
|
# |
||||||
|
# Option that takes a typeless argument. |
||||||
|
# foo.arg |
||||||
|
# |
||||||
|
# Option that takes a typed argument. Allowable types are all |
||||||
|
# valid character classes to the tcl command "string is". |
||||||
|
# Currently must be one of alnum, alpha, ascii, control, |
||||||
|
# boolean, digit, double, false, graph, integer, lower, print, |
||||||
|
# punct, space, true, upper, wordchar, or xdigit. |
||||||
|
# foo.double |
||||||
|
# |
||||||
|
# Option that takes an argument from a list. |
||||||
|
# foo.(bar|blat) |
||||||
|
# |
||||||
|
# Argument quantifier syntax: |
||||||
|
# Option that takes an optional argument. |
||||||
|
# foo.arg? |
||||||
|
# |
||||||
|
# Option that takes a list of arguments terminated by "--". |
||||||
|
# foo.arg+ |
||||||
|
# |
||||||
|
# Option that takes an optional list of arguments terminated by "--". |
||||||
|
# foo.arg* |
||||||
|
# |
||||||
|
# Argument quantifiers work on all argument types, so, for |
||||||
|
# example, the following is a valid option specification. |
||||||
|
# foo.(bar|blat|blah)? |
||||||
|
# |
||||||
|
# Argument syntax miscellany: |
||||||
|
# Options may be specified on the command line using a unique, |
||||||
|
# shortened version of the option name. Given that program foo |
||||||
|
# has an option list of {bar.alpha blah.arg blat.double}, |
||||||
|
# "foo -b fob" returns an error, but "foo -ba fob" |
||||||
|
# successfully returns {bar fob} |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The typedGetopt function returns one of the following: |
||||||
|
# 1 a valid option was found |
||||||
|
# 0 no more options found to process |
||||||
|
# -1 invalid option |
||||||
|
# -2 missing argument to a valid option |
||||||
|
# -3 argument to a valid option does not match type |
||||||
|
# |
||||||
|
# Known Bugs: |
||||||
|
# When using options which include special glob characters, |
||||||
|
# you must use the exact option. Abbreviating it can cause |
||||||
|
# an error in the "cmdline::prefixSearch" procedure. |
||||||
|
|
||||||
|
proc ::cmdline::typedGetopt {argvVar optstring optVar argVar} { |
||||||
|
variable charclasses |
||||||
|
|
||||||
|
upvar $argvVar argsList |
||||||
|
|
||||||
|
upvar $optVar retvar |
||||||
|
upvar $argVar optarg |
||||||
|
|
||||||
|
# default settings for a normal return |
||||||
|
set optarg "" |
||||||
|
set retvar "" |
||||||
|
set retval 0 |
||||||
|
|
||||||
|
# check if we're past the end of the args list |
||||||
|
if {[llength $argsList] != 0} { |
||||||
|
|
||||||
|
# if we got -- or an option that doesn't begin with -, return (skipping |
||||||
|
# the --). otherwise process the option arg. |
||||||
|
switch -glob -- [set arg [lindex $argsList 0]] { |
||||||
|
"--" { |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
} |
||||||
|
|
||||||
|
"-*" { |
||||||
|
# Create list of options without their argument extensions |
||||||
|
|
||||||
|
set optstr "" |
||||||
|
foreach str $optstring { |
||||||
|
lappend optstr [file rootname $str] |
||||||
|
} |
||||||
|
|
||||||
|
set _opt [string range $arg 1 end] |
||||||
|
|
||||||
|
set i [prefixSearch $optstr [file rootname $_opt]] |
||||||
|
if {$i != -1} { |
||||||
|
set opt [lindex $optstring $i] |
||||||
|
|
||||||
|
set quantifier "none" |
||||||
|
if {[regexp -- {\.[^.]+([?+*])$} $opt dummy quantifier]} { |
||||||
|
set opt [string range $opt 0 end-1] |
||||||
|
} |
||||||
|
|
||||||
|
if {[string first . $opt] == -1} { |
||||||
|
set retval 1 |
||||||
|
set retvar $opt |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
|
||||||
|
} elseif {[regexp -- "\\.(arg|$charclasses)\$" $opt dummy charclass] |
||||||
|
|| [regexp -- {\.\(([^)]+)\)} $opt dummy charclass]} { |
||||||
|
if {[string equal arg $charclass]} { |
||||||
|
set type arg |
||||||
|
} elseif {[regexp -- "^($charclasses)\$" $charclass]} { |
||||||
|
set type class |
||||||
|
} else { |
||||||
|
set type oneof |
||||||
|
} |
||||||
|
|
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
set opt [file rootname $opt] |
||||||
|
|
||||||
|
while {1} { |
||||||
|
if {[llength $argsList] == 0 |
||||||
|
|| [string equal "--" [lindex $argsList 0]]} { |
||||||
|
if {[string equal "--" [lindex $argsList 0]]} { |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
} |
||||||
|
|
||||||
|
set oneof "" |
||||||
|
if {$type == "arg"} { |
||||||
|
set charclass an |
||||||
|
} elseif {$type == "oneof"} { |
||||||
|
set oneof ", one of $charclass" |
||||||
|
set charclass an |
||||||
|
} |
||||||
|
|
||||||
|
if {$quantifier == "?"} { |
||||||
|
set retval 1 |
||||||
|
set retvar $opt |
||||||
|
set optarg "" |
||||||
|
} elseif {$quantifier == "+"} { |
||||||
|
set retvar $opt |
||||||
|
if {[llength $optarg] < 1} { |
||||||
|
set retval -2 |
||||||
|
set optarg "Option requires at least one $charclass argument$oneof -- $opt" |
||||||
|
} else { |
||||||
|
set retval 1 |
||||||
|
} |
||||||
|
} elseif {$quantifier == "*"} { |
||||||
|
set retval 1 |
||||||
|
set retvar $opt |
||||||
|
} else { |
||||||
|
set optarg "Option requires $charclass argument$oneof -- $opt" |
||||||
|
set retvar $opt |
||||||
|
set retval -2 |
||||||
|
} |
||||||
|
set quantifier "" |
||||||
|
} elseif {($type == "arg") |
||||||
|
|| (($type == "oneof") |
||||||
|
&& [string first "|[lindex $argsList 0]|" "|$charclass|"] != -1) |
||||||
|
|| (($type == "class") |
||||||
|
&& [string is $charclass [lindex $argsList 0]])} { |
||||||
|
set retval 1 |
||||||
|
set retvar $opt |
||||||
|
lappend optarg [lindex $argsList 0] |
||||||
|
set argsList [lrange $argsList 1 end] |
||||||
|
} else { |
||||||
|
set oneof "" |
||||||
|
if {$type == "arg"} { |
||||||
|
set charclass an |
||||||
|
} elseif {$type == "oneof"} { |
||||||
|
set oneof ", one of $charclass" |
||||||
|
set charclass an |
||||||
|
} |
||||||
|
set optarg "Option requires $charclass argument$oneof -- $opt" |
||||||
|
set retvar $opt |
||||||
|
set retval -3 |
||||||
|
|
||||||
|
if {$quantifier == "?"} { |
||||||
|
set retval 1 |
||||||
|
set optarg "" |
||||||
|
} |
||||||
|
set quantifier "" |
||||||
|
} |
||||||
|
if {![regexp -- {[+*]} $quantifier]} { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
Error \ |
||||||
|
"Illegal option type specification: must be one of $charclasses" \ |
||||||
|
BAD OPTION TYPE |
||||||
|
} |
||||||
|
} else { |
||||||
|
set optarg "Illegal option -- $_opt" |
||||||
|
set retvar $_opt |
||||||
|
set retval -1 |
||||||
|
} |
||||||
|
} |
||||||
|
default { |
||||||
|
# Skip ahead |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $retval |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::typedGetoptions -- |
||||||
|
# |
||||||
|
# Process a set of command line options, filling in defaults |
||||||
|
# for those not specified. This also generates an error message |
||||||
|
# that lists the allowed options if an incorrect option is |
||||||
|
# specified. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# argvVar The name of the argument list, typically argv |
||||||
|
# optlist A list-of-lists where each element specifies an option |
||||||
|
# in the form: |
||||||
|
# |
||||||
|
# option default comment |
||||||
|
# |
||||||
|
# Options formatting is as described for the optstring |
||||||
|
# argument of typedGetopt. Default is for optionally |
||||||
|
# specifying a default value. Comment is for optionally |
||||||
|
# specifying a comment for the usage display. The |
||||||
|
# options "--", "-help", and "-?" are automatically included |
||||||
|
# in optlist. |
||||||
|
# |
||||||
|
# Argument syntax miscellany: |
||||||
|
# Options formatting and syntax is as described in typedGetopt. |
||||||
|
# There are two additional suffixes that may be applied when |
||||||
|
# passing options to typedGetoptions. |
||||||
|
# |
||||||
|
# You may add ".multi" as a suffix to any option. For options |
||||||
|
# that take an argument, this means that the option may be used |
||||||
|
# more than once on the command line and that each additional |
||||||
|
# argument will be appended to a list, which is then returned |
||||||
|
# to the application. |
||||||
|
# foo.double.multi |
||||||
|
# |
||||||
|
# If a non-argument option is specified as ".multi", it is |
||||||
|
# toggled on and off for each time it is used on the command |
||||||
|
# line. |
||||||
|
# foo.multi |
||||||
|
# |
||||||
|
# If an option specification does not contain the ".multi" |
||||||
|
# suffix, it is not an error to use an option more than once. |
||||||
|
# In this case, the behavior for options with arguments is that |
||||||
|
# the last argument is the one that will be returned. For |
||||||
|
# options that do not take arguments, using them more than once |
||||||
|
# has no additional effect. |
||||||
|
# |
||||||
|
# Options may also be hidden from the usage display by |
||||||
|
# appending the suffix ".secret" to any option specification. |
||||||
|
# Please note that the ".secret" suffix must be the last suffix, |
||||||
|
# after any argument type specification and ".multi" suffix. |
||||||
|
# foo.xdigit.multi.secret |
||||||
|
# |
||||||
|
# Results |
||||||
|
# Name value pairs suitable for using with array set. |
||||||
|
|
||||||
|
proc ::cmdline::typedGetoptions {argvVar optlist {usage options:}} { |
||||||
|
variable charclasses |
||||||
|
|
||||||
|
upvar 1 $argvVar argv |
||||||
|
|
||||||
|
set opts {? help} |
||||||
|
foreach opt $optlist { |
||||||
|
set name [lindex $opt 0] |
||||||
|
if {[regsub -- {\.secret$} $name {} name] == 1} { |
||||||
|
# Remove this extension before passing to typedGetopt. |
||||||
|
} |
||||||
|
if {[regsub -- {\.multi$} $name {} name] == 1} { |
||||||
|
# Remove this extension before passing to typedGetopt. |
||||||
|
|
||||||
|
regsub -- {\..*$} $name {} temp |
||||||
|
set multi($temp) 1 |
||||||
|
} |
||||||
|
lappend opts $name |
||||||
|
if {[regsub -- "\\.(arg|$charclasses|\\(.+).?\$" $name {} name] == 1} { |
||||||
|
# Set defaults for those that take values. |
||||||
|
# Booleans are set just by being present, or not |
||||||
|
|
||||||
|
set dflt [lindex $opt 1] |
||||||
|
if {$dflt != {}} { |
||||||
|
set defaults($name) $dflt |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set argc [llength $argv] |
||||||
|
while {[set err [typedGetopt argv $opts opt arg]]} { |
||||||
|
if {$err == 1} { |
||||||
|
if {[info exists result($opt)] |
||||||
|
&& [info exists multi($opt)]} { |
||||||
|
# Toggle boolean options or append new arguments |
||||||
|
|
||||||
|
if {$arg == ""} { |
||||||
|
unset result($opt) |
||||||
|
} else { |
||||||
|
set result($opt) "$result($opt) $arg" |
||||||
|
} |
||||||
|
} else { |
||||||
|
set result($opt) "$arg" |
||||||
|
} |
||||||
|
} elseif {($err == -1) || ($err == -3)} { |
||||||
|
Error [typedUsage $optlist $usage] USAGE |
||||||
|
} elseif {$err == -2 && ![info exists defaults($opt)]} { |
||||||
|
Error [typedUsage $optlist $usage] USAGE |
||||||
|
} |
||||||
|
} |
||||||
|
if {[info exists result(?)] || [info exists result(help)]} { |
||||||
|
Error [typedUsage $optlist $usage] USAGE |
||||||
|
} |
||||||
|
foreach {opt dflt} [array get defaults] { |
||||||
|
if {![info exists result($opt)]} { |
||||||
|
set result($opt) $dflt |
||||||
|
} |
||||||
|
} |
||||||
|
return [array get result] |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::typedUsage -- |
||||||
|
# |
||||||
|
# Generate an error message that lists the allowed flags, |
||||||
|
# type of argument taken (if any), default value (if any), |
||||||
|
# and an optional description. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# optlist As for cmdline::typedGetoptions |
||||||
|
# |
||||||
|
# Results |
||||||
|
# A formatted usage message |
||||||
|
|
||||||
|
proc ::cmdline::typedUsage {optlist {usage {options:}}} { |
||||||
|
variable charclasses |
||||||
|
|
||||||
|
set str "[getArgv0] $usage\n" |
||||||
|
set longest 20 |
||||||
|
set lines {} |
||||||
|
foreach opt [concat $optlist \ |
||||||
|
{{help "Print this message"} {? "Print this message"}}] { |
||||||
|
set name "-[lindex $opt 0]" |
||||||
|
if {[regsub -- {\.secret$} $name {} name] == 1} { |
||||||
|
# Hidden option |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if {[regsub -- {\.multi$} $name {} name] == 1} { |
||||||
|
# Display something about multiple options |
||||||
|
} |
||||||
|
|
||||||
|
if {[regexp -- "\\.(arg|$charclasses)\$" $name dummy charclass] || |
||||||
|
[regexp -- {\.\(([^)]+)\)} $opt dummy charclass] |
||||||
|
} { |
||||||
|
regsub -- "\\..+\$" $name {} name |
||||||
|
append name " $charclass" |
||||||
|
set desc [lindex $opt 2] |
||||||
|
set default [lindex $opt 1] |
||||||
|
if {$default != ""} { |
||||||
|
append desc " <$default>" |
||||||
|
} |
||||||
|
} else { |
||||||
|
set desc [lindex $opt 1] |
||||||
|
} |
||||||
|
lappend accum $name $desc |
||||||
|
set n [string length $name] |
||||||
|
if {$n > $longest} { set longest $n } |
||||||
|
# max not available before 8.5 - set longest [expr {max($longest, [string length $name])}] |
||||||
|
} |
||||||
|
foreach {name desc} $accum { |
||||||
|
append str "[string trimright [format " %-*s %s" $longest $name $desc]]\n" |
||||||
|
} |
||||||
|
return $str |
||||||
|
} |
||||||
|
|
||||||
|
# ::cmdline::prefixSearch -- |
||||||
|
# |
||||||
|
# Search a Tcl list for a pattern; searches first for an exact match, |
||||||
|
# and if that fails, for a unique prefix that matches the pattern |
||||||
|
# (i.e, first "lsearch -exact", then "lsearch -glob $pattern*" |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# list list of words |
||||||
|
# pattern word to search for |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# Index of found word is returned. If no exact match or |
||||||
|
# unique short version is found then -1 is returned. |
||||||
|
|
||||||
|
proc ::cmdline::prefixSearch {list pattern} { |
||||||
|
# Check for an exact match |
||||||
|
|
||||||
|
if {[set pos [::lsearch -exact $list $pattern]] > -1} { |
||||||
|
return $pos |
||||||
|
} |
||||||
|
|
||||||
|
# Check for a unique short version |
||||||
|
|
||||||
|
set slist [lsort $list] |
||||||
|
if {[set pos [::lsearch -glob $slist $pattern*]] > -1} { |
||||||
|
# What if there is nothing for the check variable? |
||||||
|
|
||||||
|
set check [lindex $slist [expr {$pos + 1}]] |
||||||
|
if {[string first $pattern $check] != 0} { |
||||||
|
return [::lsearch -exact $list [lindex $slist $pos]] |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
} |
||||||
|
# ::cmdline::Error -- |
||||||
|
# |
||||||
|
# Internal helper to throw errors with a proper error-code attached. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# message text of the error message to throw. |
||||||
|
# args additional parts of the error code to use, |
||||||
|
# with CMDLINE as basic prefix added by this command. |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# An error is thrown, always. |
||||||
|
|
||||||
|
proc ::cmdline::Error {message args} { |
||||||
|
return -code error -errorcode [linsert $args 0 CMDLINE] $message |
||||||
|
} |
@ -0,0 +1,2 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded cmdline 1.5.3 [list source [file join $dir cmdline.tcl]] |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@ |
|||||||
|
if {![package vsatisfies [package provide Tcl] 8.5 9]} {return} |
||||||
|
package ifneeded comm 4.7.3 [list source [file join $dir comm.tcl]] |
@ -0,0 +1,72 @@ |
|||||||
|
# ascaller.tcl - |
||||||
|
# |
||||||
|
# A few utility procs that manage the evaluation of a command |
||||||
|
# or a script in the context of a caller, taking care of all |
||||||
|
# the ugly details of proper return codes, errorcodes, and |
||||||
|
# a good stack trace in ::errorInfo as appropriate. |
||||||
|
# ------------------------------------------------------------------------- |
||||||
|
# |
||||||
|
# RCS: @(#) $Id: ascaller.tcl,v 1.3 2004/01/15 06:36:12 andreas_kupries Exp $ |
||||||
|
|
||||||
|
namespace eval ::control { |
||||||
|
|
||||||
|
proc CommandAsCaller {cmdVar resultVar {where {}} {codeVar code}} { |
||||||
|
set x [expr {[string equal "" $where] |
||||||
|
? {} : [subst -nobackslashes {\n ($where)}]}] |
||||||
|
set script [subst -nobackslashes -nocommands { |
||||||
|
set $codeVar [catch {uplevel 1 $$cmdVar} $resultVar] |
||||||
|
if {$$codeVar > 1} { |
||||||
|
return -code $$codeVar $$resultVar |
||||||
|
} |
||||||
|
if {$$codeVar == 1} { |
||||||
|
if {[string equal {"uplevel 1 $$cmdVar"} \ |
||||||
|
[lindex [split [set ::errorInfo] \n] end]]} { |
||||||
|
set $codeVar [join \ |
||||||
|
[lrange [split [set ::errorInfo] \n] 0 \ |
||||||
|
end-[expr {4+[llength [split $$cmdVar \n]]}]] \n] |
||||||
|
} else { |
||||||
|
set $codeVar [join \ |
||||||
|
[lrange [split [set ::errorInfo] \n] 0 end-1] \n] |
||||||
|
} |
||||||
|
return -code error -errorcode [set ::errorCode] \ |
||||||
|
-errorinfo "$$codeVar$x" $$resultVar |
||||||
|
} |
||||||
|
}] |
||||||
|
return $script |
||||||
|
} |
||||||
|
|
||||||
|
proc BodyAsCaller {bodyVar resultVar codeVar {where {}}} { |
||||||
|
set x [expr {[string equal "" $where] |
||||||
|
? {} : [subst -nobackslashes -nocommands \ |
||||||
|
{\n ($where[string map {{ ("uplevel"} {}} \ |
||||||
|
[lindex [split [set ::errorInfo] \n] end]]}]}] |
||||||
|
set script [subst -nobackslashes -nocommands { |
||||||
|
set $codeVar [catch {uplevel 1 $$bodyVar} $resultVar] |
||||||
|
if {$$codeVar == 1} { |
||||||
|
if {[string equal {"uplevel 1 $$bodyVar"} \ |
||||||
|
[lindex [split [set ::errorInfo] \n] end]]} { |
||||||
|
set ::errorInfo [join \ |
||||||
|
[lrange [split [set ::errorInfo] \n] 0 end-2] \n] |
||||||
|
} |
||||||
|
set $codeVar [join \ |
||||||
|
[lrange [split [set ::errorInfo] \n] 0 end-1] \n] |
||||||
|
return -code error -errorcode [set ::errorCode] \ |
||||||
|
-errorinfo "$$codeVar$x" $$resultVar |
||||||
|
} |
||||||
|
}] |
||||||
|
return $script |
||||||
|
} |
||||||
|
|
||||||
|
proc ErrorInfoAsCaller {find replace} { |
||||||
|
set info $::errorInfo |
||||||
|
set i [string last "\n (\"$find" $info] |
||||||
|
if {$i == -1} {return $info} |
||||||
|
set result [string range $info 0 [incr i 6]] ;# keep "\n (\"" |
||||||
|
append result $replace ;# $find -> $replace |
||||||
|
incr i [string length $find] |
||||||
|
set j [string first ) $info [incr i]] ;# keep rest of parenthetical |
||||||
|
append result [string range $info $i $j] |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue