Julian Noble
7 months ago
46 changed files with 1446 additions and 53 deletions
@ -0,0 +1,83 @@
|
||||
#A basic utility script to cat a file to another with optional string prefix and suffix |
||||
#does not interpret escapes e.g \n in arguments - review |
||||
#Used for example by zig.build or other systems to avoid problems with redirecting echo/cat etc |
||||
#2024 - zig.build.addSystemCommand doesn't seem to support pipelines |
||||
|
||||
#padding |
||||
#padding |
||||
#padding |
||||
#padding |
||||
#padding |
||||
#padding |
||||
#padding |
||||
|
||||
set usage "usage: interpreter scriptname -startnl 0|1 -prefix <string> -prefixnl 0|1 -suffix <string> -suffixnl 0|1 -input <filename> -inputnl 0|1 -output <filename>" |
||||
set defaults [dict create\ |
||||
-startnl 0\ |
||||
-crlf 0\ |
||||
-prefix ""\ |
||||
-prefixnl 0\ |
||||
-suffix ""\ |
||||
-suffixnl 0\ |
||||
-input ""\ |
||||
-inputnl 0\ |
||||
-output \uFFEF\ |
||||
] |
||||
if {"windows" eq $::tcl_platform(platform)} { |
||||
package require punk::winrun |
||||
package require twapi |
||||
set rawcmdline [twapi::get_process_commandline [pid]] |
||||
set allargs [punk::winrun::unquote_wintcl $rawcmdline] |
||||
#first 2 args are the interpreter and the script |
||||
set scriptargs [lrange $allargs 2 end] |
||||
} else { |
||||
set scriptargs $::argv |
||||
} |
||||
#puts stdout "scriptargs:$scriptargs" |
||||
if {[llength $scriptargs] % 2 != 0} { |
||||
puts stderr $usage |
||||
exit 1 |
||||
} |
||||
set opts [dict merge $defaults $scriptargs] |
||||
#puts stdout "opts:$opts" |
||||
if {[dict get $opts -output] eq "\uFFEF"} { |
||||
puts stderr $usage |
||||
exit 2 |
||||
} |
||||
set infile [dict get $opts -input] |
||||
set filedata "" |
||||
if {$infile ne ""} { |
||||
if {![file exists $infile]} { |
||||
puts stderr "Unable to read input file '$infile'" |
||||
exit 3 |
||||
} |
||||
set fd [open $infile r] |
||||
set filedata [read $fd] |
||||
close $fd |
||||
} |
||||
set startnl "" |
||||
set prefixnl "" |
||||
set suffixnl "" |
||||
set inputnl "" |
||||
if {[dict get $opts -startnl]} { |
||||
set startnl \n |
||||
} |
||||
if {[dict get $opts -prefixnl]} { |
||||
set prefixnl \n |
||||
} |
||||
if {[dict get $opts -suffixnl]} { |
||||
set suffixnl \n |
||||
} |
||||
if {[dict get $opts -inputnl]} { |
||||
set inputnl \n |
||||
} |
||||
set data "$startnl[dict get $opts -prefix]$prefixnl$filedata$inputnl[dict get $opts -suffix]$suffixnl" |
||||
set fdout [open [dict get $opts -output] w] |
||||
if {[dict get $opts -crlf] == 0} { |
||||
chan configure $fdout -translation binary |
||||
} |
||||
puts -nonewline $fdout $data |
||||
close $fdout |
||||
catch {puts stdout ok} |
||||
exit 0 |
||||
|
@ -0,0 +1,410 @@
|
||||
# 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.2- |
||||
namespace eval ::base64 { |
||||
namespace export encode decode |
||||
} |
||||
|
||||
package provide base64 2.5 |
||||
|
||||
if {[package vsatisfies [package require Tcl] 8.6]} { |
||||
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 |
||||
|
||||
set i 0 |
||||
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,306 @@
|
||||
# Debug - a debug narrative logger. |
||||
# -- Colin McCormack / originally Wub server utilities |
||||
# |
||||
# Debugging areas of interest are represented by 'tokens' which have |
||||
# independantly settable levels of interest (an integer, higher is more detailed) |
||||
# |
||||
# Debug narrative is provided as a tcl script whose value is [subst]ed in the |
||||
# caller's scope if and only if the current level of interest matches or exceeds |
||||
# the Debug call's level of detail. This is useful, as one can place arbitrarily |
||||
# complex narrative in code without unnecessarily evaluating it. |
||||
# |
||||
# TODO: potentially different streams for different areas of interest. |
||||
# (currently only stderr is used. there is some complexity in efficient |
||||
# cross-threaded streams.) |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Requisites |
||||
|
||||
package require Tcl 8.5- |
||||
|
||||
namespace eval ::debug { |
||||
namespace export -clear \ |
||||
define on off prefix suffix header trailer \ |
||||
names 2array level setting parray pdict \ |
||||
nl tab hexl |
||||
namespace ensemble create -subcommands {} |
||||
} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## API & Implementation |
||||
|
||||
proc ::debug::noop {args} {} |
||||
|
||||
proc ::debug::debug {tag message {level 1}} { |
||||
variable detail |
||||
if {$detail($tag) < $level} { |
||||
#puts stderr "$tag @@@ $detail($tag) >= $level" |
||||
return |
||||
} |
||||
|
||||
variable prefix |
||||
variable suffix |
||||
variable header |
||||
variable trailer |
||||
variable fds |
||||
|
||||
if {[info exists fds($tag)]} { |
||||
set fd $fds($tag) |
||||
} else { |
||||
set fd stderr |
||||
} |
||||
|
||||
# Assemble the shown text from the user message and the various |
||||
# prefixes and suffices (global + per-tag). |
||||
|
||||
set themessage "" |
||||
if {[info exists prefix(::)]} { append themessage $prefix(::) } |
||||
if {[info exists prefix($tag)]} { append themessage $prefix($tag) } |
||||
append themessage $message |
||||
if {[info exists suffix($tag)]} { append themessage $suffix($tag) } |
||||
if {[info exists suffix(::)]} { append themessage $suffix(::) } |
||||
|
||||
# Resolve variables references and command invokations embedded |
||||
# into the message with plain text. |
||||
set code [catch { |
||||
set smessage [uplevel 1 [list ::subst -nobackslashes $themessage]] |
||||
set sheader [uplevel 1 [list ::subst -nobackslashes $header]] |
||||
set strailer [uplevel 1 [list ::subst -nobackslashes $trailer]] |
||||
} __ eo] |
||||
|
||||
# And dump an internal error if that resolution failed. |
||||
if {$code} { |
||||
if {[catch { |
||||
set caller [info level -1] |
||||
}]} { set caller GLOBAL } |
||||
if {[string length $caller] >= 1000} { |
||||
set caller "[string range $caller 0 200]...[string range $caller end-200 end]" |
||||
} |
||||
foreach line [split $caller \n] { |
||||
puts -nonewline $fd "@@(DebugError from $tag ($eo): $line)" |
||||
} |
||||
return |
||||
} |
||||
|
||||
# From here we have a good message to show. We only shorten it a |
||||
# bit if its a bit excessive in size. |
||||
|
||||
if {[string length $smessage] > 4096} { |
||||
set head [string range $smessage 0 2048] |
||||
set tail [string range $smessage end-2048 end] |
||||
set smessage "${head}...(truncated)...$tail" |
||||
} |
||||
|
||||
foreach line [split $smessage \n] { |
||||
puts $fd "$sheader$tag | $line$strailer" |
||||
} |
||||
return |
||||
} |
||||
|
||||
# names - return names of debug tags |
||||
proc ::debug::names {} { |
||||
variable detail |
||||
return [lsort [array names detail]] |
||||
} |
||||
|
||||
proc ::debug::2array {} { |
||||
variable detail |
||||
set result {} |
||||
foreach n [lsort [array names detail]] { |
||||
if {[interp alias {} debug.$n] ne "::debug::noop"} { |
||||
lappend result $n $detail($n) |
||||
} else { |
||||
lappend result $n -$detail($n) |
||||
} |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
# level - set level and fd for tag |
||||
proc ::debug::level {tag {level ""} {fd {}}} { |
||||
variable detail |
||||
# TODO: Force level >=0. |
||||
if {$level ne ""} { |
||||
set detail($tag) $level |
||||
} |
||||
|
||||
if {![info exists detail($tag)]} { |
||||
set detail($tag) 1 |
||||
} |
||||
|
||||
variable fds |
||||
if {$fd ne {}} { |
||||
set fds($tag) $fd |
||||
} |
||||
|
||||
return $detail($tag) |
||||
} |
||||
|
||||
proc ::debug::header {text} { variable header $text } |
||||
proc ::debug::trailer {text} { variable trailer $text } |
||||
|
||||
proc ::debug::define {tag} { |
||||
if {[interp alias {} debug.$tag] ne {}} return |
||||
off $tag |
||||
return |
||||
} |
||||
|
||||
# Set a prefix/suffix to use for tag. |
||||
# The global (tag-independent) prefix/suffix is adressed through tag '::'. |
||||
# This works because colon (:) is an illegal character for user-specified tags. |
||||
|
||||
proc ::debug::prefix {tag {theprefix {}}} { |
||||
variable prefix |
||||
set prefix($tag) $theprefix |
||||
|
||||
if {[interp alias {} debug.$tag] ne {}} return |
||||
off $tag |
||||
return |
||||
} |
||||
|
||||
proc ::debug::suffix {tag {theprefix {}}} { |
||||
variable suffix |
||||
set suffix($tag) $theprefix |
||||
|
||||
if {[interp alias {} debug.$tag] ne {}} return |
||||
off $tag |
||||
return |
||||
} |
||||
|
||||
# turn on debugging for tag |
||||
proc ::debug::on {tag {level ""} {fd {}}} { |
||||
variable active |
||||
set active($tag) 1 |
||||
level $tag $level $fd |
||||
interp alias {} debug.$tag {} ::debug::debug $tag |
||||
return |
||||
} |
||||
|
||||
# turn off debugging for tag |
||||
proc ::debug::off {tag {level ""} {fd {}}} { |
||||
variable active |
||||
set active($tag) 1 |
||||
level $tag $level $fd |
||||
interp alias {} debug.$tag {} ::debug::noop |
||||
return |
||||
} |
||||
|
||||
proc ::debug::setting {args} { |
||||
if {[llength $args] == 1} { |
||||
set args [lindex $args 0] |
||||
} |
||||
set fd stderr |
||||
if {[llength $args] % 2} { |
||||
set fd [lindex $args end] |
||||
set args [lrange $args 0 end-1] |
||||
} |
||||
foreach {tag level} $args { |
||||
if {$level > 0} { |
||||
level $tag $level $fd |
||||
interp alias {} debug.$tag {} ::debug::debug $tag |
||||
} else { |
||||
level $tag [expr {-$level}] $fd |
||||
interp alias {} debug.$tag {} ::debug::noop |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Convenience commands. |
||||
# Format arrays and dicts as multi-line message. |
||||
# Insert newlines and tabs. |
||||
|
||||
proc ::debug::nl {} { return \n } |
||||
proc ::debug::tab {} { return \t } |
||||
|
||||
proc ::debug::parray {a {pattern *}} { |
||||
upvar 1 $a array |
||||
if {![array exists array]} { |
||||
error "\"$a\" isn't an array" |
||||
} |
||||
pdict [array get array] $pattern |
||||
} |
||||
|
||||
proc ::debug::pdict {dict {pattern *}} { |
||||
set maxl 0 |
||||
set names [lsort -dict [dict keys $dict $pattern]] |
||||
foreach name $names { |
||||
if {[string length $name] > $maxl} { |
||||
set maxl [string length $name] |
||||
} |
||||
} |
||||
set maxl [expr {$maxl + 2}] |
||||
set lines {} |
||||
foreach name $names { |
||||
set nameString [format (%s) $name] |
||||
lappend lines [format "%-*s = %s" \ |
||||
$maxl $nameString \ |
||||
[dict get $dict $name]] |
||||
} |
||||
return [join $lines \n] |
||||
} |
||||
|
||||
proc ::debug::hexl {data {prefix {}}} { |
||||
set r {} |
||||
|
||||
# Convert the data to hex and to characters. |
||||
binary scan $data H*@0a* hexa asciia |
||||
|
||||
# Replace non-printing characters in the data with dots. |
||||
regsub -all -- {[^[:graph:] ]} $asciia {.} asciia |
||||
|
||||
# Pad with spaces to a full multiple of 32/16. |
||||
set n [expr {[string length $hexa] % 32}] |
||||
if {$n < 32} { append hexa [string repeat { } [expr {32-$n}]] } |
||||
#puts "pad H [expr {32-$n}]" |
||||
|
||||
set n [expr {[string length $asciia] % 32}] |
||||
if {$n < 16} { append asciia [string repeat { } [expr {16-$n}]] } |
||||
#puts "pad A [expr {32-$n}]" |
||||
|
||||
# Reassemble formatted, in groups of 16 bytes/characters. |
||||
# The hex part is handled in groups of 32 nibbles. |
||||
set addr 0 |
||||
while {[string length $hexa]} { |
||||
# Get front group of 16 bytes each. |
||||
set hex [string range $hexa 0 31] |
||||
set ascii [string range $asciia 0 15] |
||||
# Prep for next iteration |
||||
set hexa [string range $hexa 32 end] |
||||
set asciia [string range $asciia 16 end] |
||||
|
||||
# Convert the hex to pairs of hex digits |
||||
regsub -all -- {..} $hex {& } hex |
||||
|
||||
# Add the hex and latin-1 data to the result buffer |
||||
append r $prefix [format %04x $addr] { | } $hex { |} $ascii |\n |
||||
incr addr 16 |
||||
} |
||||
|
||||
# And done |
||||
return $r |
||||
} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
|
||||
namespace eval debug { |
||||
variable detail ; # map: TAG -> level of interest |
||||
variable prefix ; # map: TAG -> message prefix to use |
||||
variable suffix ; # map: TAG -> message suffix to use |
||||
variable fds ; # map: TAG -> handle of open channel to log to. |
||||
variable header {} ; # per-line heading, subst'ed |
||||
variable trailer {} ; # per-line ending, subst'ed |
||||
|
||||
# Notes: |
||||
# - The tag '::' is reserved. "prefix" and "suffix" use it to store |
||||
# the global message prefix / suffix. |
||||
# - prefix and suffix are applied per message. |
||||
# - header and trailer are per line. And should not generate multiple lines! |
||||
} |
||||
|
||||
# # ## ### ##### ######## ############# ##################### |
||||
## Ready |
||||
|
||||
package provide debug 1.0.6 |
||||
return |
@ -0,0 +1,56 @@
|
||||
# -*- tcl -*- |
||||
# ### ### ### ######### ######### ######### |
||||
## Terminal packages - ANSI |
||||
## Generic commands to define commands for code sequences. |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Requirements |
||||
|
||||
namespace eval ::term::ansi::code {} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## API. Escape clauses, plain and bracket |
||||
## Used by 'define'd commands. |
||||
|
||||
proc ::term::ansi::code::esc {str} {return \033$str} |
||||
proc ::term::ansi::code::escb {str} {esc \[$str} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## API. Define command for named control code, or constant. |
||||
## (Simple definitions without arguments) |
||||
|
||||
proc ::term::ansi::code::define {name escape code} { |
||||
proc [Qualified $name] {} [list ::term::ansi::code::$escape $code] |
||||
} |
||||
|
||||
proc ::term::ansi::code::const {name code} { |
||||
proc [Qualified $name] {} [list return $code] |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Internal helper to construct fully-qualified names. |
||||
|
||||
proc ::term::ansi::code::Qualified {name} { |
||||
if {![string match ::* $name]} { |
||||
# Get the caller's namespace; append :: if it is not the |
||||
# global namespace, for separation from the actual name. |
||||
set ns [uplevel 2 [list namespace current]] |
||||
if {$ns ne "::"} {append ns ::} |
||||
set name $ns$name |
||||
} |
||||
return $name |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
|
||||
namespace eval ::term::ansi::code { |
||||
namespace export esc escb define const |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Ready |
||||
|
||||
package provide term::ansi::code 0.2 |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
@ -0,0 +1,108 @@
|
||||
# -*- tcl -*- |
||||
# ### ### ### ######### ######### ######### |
||||
## Terminal packages - ANSI - Attribute codes |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Requirements |
||||
|
||||
package require term::ansi::code ; # Constants |
||||
|
||||
namespace eval ::term::ansi::code::attr {} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## API. Symbolic names. |
||||
|
||||
proc ::term::ansi::code::attr::names {} { |
||||
variable attr |
||||
return $attr |
||||
} |
||||
|
||||
proc ::term::ansi::code::attr::import {{ns attr} args} { |
||||
if {![llength $args]} {set args *} |
||||
set args ::term::ansi::code::attr::[join $args " ::term::ansi::code::attr::"] |
||||
uplevel 1 [list namespace eval ${ns} [linsert $args 0 namespace import]] |
||||
return |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Internal - Setup |
||||
|
||||
proc ::term::ansi::code::attr::DEF {name value} { |
||||
variable attr |
||||
const $name $value |
||||
lappend attr $name |
||||
namespace export $name |
||||
return |
||||
} |
||||
|
||||
proc ::term::ansi::code::attr::INIT {} { |
||||
# ### ### ### ######### ######### ######### |
||||
## |
||||
|
||||
# Colors. Foreground <=> Text |
||||
DEF fgblack 30 ; # Black |
||||
DEF fgred 31 ; # Red |
||||
DEF fggreen 32 ; # Green |
||||
DEF fgyellow 33 ; # Yellow |
||||
DEF fgblue 34 ; # Blue |
||||
DEF fgmagenta 35 ; # Magenta |
||||
DEF fgcyan 36 ; # Cyan |
||||
DEF fgwhite 37 ; # White |
||||
DEF fgdefault 39 ; # Default (Black) |
||||
|
||||
# Colors. Background. |
||||
DEF bgblack 40 ; # Black |
||||
DEF bgred 41 ; # Red |
||||
DEF bggreen 42 ; # Green |
||||
DEF bgyellow 43 ; # Yellow |
||||
DEF bgblue 44 ; # Blue |
||||
DEF bgmagenta 45 ; # Magenta |
||||
DEF bgcyan 46 ; # Cyan |
||||
DEF bgwhite 47 ; # White |
||||
DEF bgdefault 49 ; # Default (Transparent) |
||||
|
||||
# Non-color attributes. Activation. |
||||
DEF bold 1 ; # Bold |
||||
DEF dim 2 ; # Dim |
||||
DEF italic 3 ; # Italics |
||||
DEF underline 4 ; # Underscore |
||||
DEF blink 5 ; # Blink |
||||
DEF revers 7 ; # Reverse |
||||
DEF hidden 8 ; # Hidden |
||||
DEF strike 9 ; # StrikeThrough |
||||
|
||||
# Non-color attributes. Deactivation. |
||||
DEF nobold 22 ; # Bold |
||||
DEF nodim __ ; # Dim |
||||
DEF noitalic 23 ; # Italics |
||||
DEF nounderline 24 ; # Underscore |
||||
DEF noblink 25 ; # Blink |
||||
DEF norevers 27 ; # Reverse |
||||
DEF nohidden 28 ; # Hidden |
||||
DEF nostrike 29 ; # StrikeThrough |
||||
|
||||
# Remainder |
||||
DEF reset 0 ; # Reset |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
||||
return |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Data structures. |
||||
|
||||
namespace eval ::term::ansi::code::attr { |
||||
namespace import ::term::ansi::code::const |
||||
variable attr {} |
||||
} |
||||
|
||||
::term::ansi::code::attr::INIT |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Ready |
||||
|
||||
package provide term::ansi::code::attr 0.1 |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
@ -0,0 +1,272 @@
|
||||
# -*- tcl -*- |
||||
# ### ### ### ######### ######### ######### |
||||
## Terminal packages - ANSI - Control codes |
||||
|
||||
## References |
||||
# [0] Google: ansi terminal control |
||||
# [1] http://vt100.net/docs/vt100-ug/chapter3.html |
||||
# [2] http://www.termsys.demon.co.uk/vtansi.htm |
||||
# [3] http://rrbrandt.dyndns.org:60000/docs/tut/redes/ansi.php |
||||
# [4] http://www.dee.ufcg.edu.br/~rrbrandt/tools/ansi.html |
||||
# [5] http://www.ecma-international.org/publications/standards/Ecma-048.htm |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Requirements |
||||
|
||||
package require term::ansi::code |
||||
package require term::ansi::code::attr |
||||
|
||||
namespace eval ::term::ansi::code::ctrl {} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## API. Symbolic names. |
||||
|
||||
proc ::term::ansi::code::ctrl::names {} { |
||||
variable ctrl |
||||
return $ctrl |
||||
} |
||||
|
||||
proc ::term::ansi::code::ctrl::import {{ns ctrl} args} { |
||||
if {![llength $args]} {set args *} |
||||
set args ::term::ansi::code::ctrl::[join $args " ::term::ansi::code::ctrl::"] |
||||
uplevel 1 [list namespace eval $ns [linsert $args 0 namespace import]] |
||||
return |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
|
||||
## TODO = symbolic key codes for skd. |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Internal - Setup |
||||
|
||||
proc ::term::ansi::code::ctrl::DEF {name esc value} { |
||||
variable ctrl |
||||
define $name $esc $value |
||||
lappend ctrl $name |
||||
namespace export $name |
||||
return |
||||
} |
||||
|
||||
proc ::term::ansi::code::ctrl::DEFC {name arguments script} { |
||||
variable ctrl |
||||
proc $name $arguments $script |
||||
lappend ctrl $name |
||||
namespace export $name |
||||
return |
||||
} |
||||
|
||||
proc ::term::ansi::code::ctrl::INIT {} { |
||||
# ### ### ### ######### ######### ######### |
||||
## |
||||
|
||||
# Erasing |
||||
|
||||
DEF eeol escb K ; # Erase (to) End Of Line |
||||
DEF esol escb 1K ; # Erase (to) Start Of Line |
||||
DEF el escb 2K ; # Erase (current) Line |
||||
DEF ed escb J ; # Erase Down (to bottom) |
||||
DEF eu escb 1J ; # Erase Up (to top) |
||||
DEF es escb 2J ; # Erase Screen |
||||
|
||||
# Scrolling |
||||
|
||||
DEF sd esc D ; # Scroll Down |
||||
DEF su esc M ; # Scroll Up |
||||
|
||||
# Cursor Handling |
||||
|
||||
DEF ch escb H ; # Cursor Home |
||||
DEF sc escb s ; # Save Cursor |
||||
DEF rc escb u ; # Restore Cursor (Unsave) |
||||
DEF sca esc 7 ; # Save Cursor + Attributes |
||||
DEF rca esc 8 ; # Restore Cursor + Attributes |
||||
|
||||
# Tabbing |
||||
|
||||
DEF st esc H ; # Set Tab (@ current position) |
||||
DEF ct escb g ; # Clear Tab (@ current position) |
||||
DEF cat escb 3g ; # Clear All Tabs |
||||
|
||||
# Device Introspection |
||||
|
||||
DEF qdc escb c ; # Query Device Code |
||||
DEF qds escb 5n ; # Query Device Status |
||||
DEF qcp escb 6n ; # Query Cursor Position |
||||
DEF rd esc c ; # Reset Device |
||||
|
||||
# Linewrap on/off |
||||
|
||||
DEF elw escb 7h ; # Enable Line Wrap |
||||
DEF dlw escb 7l ; # Disable Line Wrap |
||||
|
||||
# Graphics Mode (aka use alternate font on/off) |
||||
|
||||
DEF eg esc F ; # Enter Graphics Mode |
||||
DEF lg esc G ; # Exit Graphics Mode |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Complex, parameterized codes |
||||
|
||||
# Select Character Set |
||||
# Choose which char set is used for default and |
||||
# alternate font. This does not change whether |
||||
# default or alternate font are used |
||||
|
||||
DEFC scs0 {tag} {esc ($tag} ; # Set default character set |
||||
DEFC scs1 {tag} {esc )$tag} ; # Set alternate character set |
||||
|
||||
# tags in A : United Kingdom Set |
||||
# B : ASCII Set |
||||
# 0 : Special Graphics |
||||
# 1 : Alternate Character ROM Standard Character Set |
||||
# 2 : Alternate Character ROM Special Graphics |
||||
|
||||
# Set Display Attributes |
||||
|
||||
DEFC sda {args} {escb [join $args \;]m} |
||||
|
||||
# Force Cursor Position (aka Go To) |
||||
|
||||
DEFC fcp {r c} {escb ${r}\;${c}f} |
||||
|
||||
# Cursor Up, Down, Forward, Backward |
||||
|
||||
DEFC cu {{n 1}} {escb [expr {$n == 1 ? "A" : "${n}A"}]} |
||||
DEFC cd {{n 1}} {escb [expr {$n == 1 ? "B" : "${n}B"}]} |
||||
DEFC cf {{n 1}} {escb [expr {$n == 1 ? "C" : "${n}C"}]} |
||||
DEFC cb {{n 1}} {escb [expr {$n == 1 ? "D" : "${n}D"}]} |
||||
|
||||
# Scroll Screen (entire display, or between rows start end, inclusive). |
||||
|
||||
DEFC ss {args} { |
||||
if {[llength $args] == 0} {return [escb r]} |
||||
if {[llength $args] == 2} {foreach {s e} $args break ; return [escb ${s};${e}r]} |
||||
return -code error "wrong\#args" |
||||
} |
||||
|
||||
# Set Key Definition |
||||
|
||||
DEFC skd {code str} {escb $code\;\"$str\"p} |
||||
|
||||
# Terminal title |
||||
|
||||
DEFC title {str} {esc \]0\;$str\007} |
||||
|
||||
# Switch to and from character/box graphics. |
||||
|
||||
DEFC gron {} {esc (0} |
||||
DEFC groff {} {esc (B} |
||||
|
||||
# Character graphics, box symbols |
||||
# - 4 corners, 4 t-junctions, |
||||
# one 4-way junction, 2 lines |
||||
|
||||
DEFC tlc {} {return [gron]l[groff]} ; # Top Left Corner |
||||
DEFC trc {} {return [gron]k[groff]} ; # Top Right Corner |
||||
DEFC brc {} {return [gron]j[groff]} ; # Bottom Right Corner |
||||
DEFC blc {} {return [gron]m[groff]} ; # Bottom Left Corner |
||||
|
||||
DEFC ltj {} {return [gron]t[groff]} ; # Left T Junction |
||||
DEFC ttj {} {return [gron]w[groff]} ; # Top T Junction |
||||
DEFC rtj {} {return [gron]u[groff]} ; # Right T Junction |
||||
DEFC btj {} {return [gron]v[groff]} ; # Bottom T Junction |
||||
|
||||
DEFC fwj {} {return [gron]n[groff]} ; # Four-Way Junction |
||||
|
||||
DEFC hl {} {return [gron]q[groff]} ; # Horizontal Line |
||||
DEFC vl {} {return [gron]x[groff]} ; # Vertical Line |
||||
|
||||
# Optimize character graphics. The generator commands above create |
||||
# way to many superfluous commands shifting into and out of the |
||||
# graphics mode. The command below removes all shifts which are |
||||
# not needed. To this end it also knows which characters will look |
||||
# the same in both modes, to handle strings created outside this |
||||
# package. |
||||
|
||||
DEFC groptim {string} { |
||||
variable grforw |
||||
variable grback |
||||
set offon [groff][gron] |
||||
set onoff [gron][groff] |
||||
while {![string equal $string [set new [string map \ |
||||
[list $offon {} $onoff {}] [string map \ |
||||
$grback [string map \ |
||||
$grforw $string]]]]]} { |
||||
set string $new |
||||
} |
||||
return $string |
||||
} |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Higher level operations |
||||
|
||||
# Clear screen <=> CursorHome + EraseDown |
||||
# Init (Fonts): Default ASCII, Alternate Graphics |
||||
# Show a block of text at a specific location. |
||||
|
||||
DEFC clear {} {return [ch][ed]} |
||||
DEFC init {} {return [scs0 B][scs1 0]} |
||||
|
||||
DEFC showat {r c text} { |
||||
if {![string length $text]} {return {}} |
||||
return [fcp $r $c][sca][join \ |
||||
[split $text \n] \ |
||||
[rca][cd][sca]][rca][cd] |
||||
} |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Attribute control (single attributes) |
||||
|
||||
foreach a [::term::ansi::code::attr::names] { |
||||
DEF sda_$a escb [::term::ansi::code::attr::$a]m |
||||
} |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
||||
return |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Data structures. |
||||
|
||||
namespace eval ::term::ansi::code::ctrl { |
||||
namespace import ::term::ansi::code::define |
||||
namespace import ::term::ansi::code::esc |
||||
namespace import ::term::ansi::code::escb |
||||
|
||||
variable grforw |
||||
variable grback |
||||
variable _ |
||||
|
||||
foreach _ { |
||||
! \" # $ % & ' ( ) * + , - . / |
||||
0 1 2 3 4 5 6 7 8 9 : ; < = > |
||||
? @ 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 [ ^ |
||||
\\ ] |
||||
} { |
||||
lappend grforw \016$_ $_\016 |
||||
lappend grback $_\017 \017$_ |
||||
} |
||||
unset _ |
||||
} |
||||
|
||||
::term::ansi::code::ctrl::INIT |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Ready |
||||
|
||||
package provide term::ansi::code::ctrl 0.3 |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
@ -0,0 +1,93 @@
|
||||
# -*- tcl -*- |
||||
# ### ### ### ######### ######### ######### |
||||
## Terminal packages - ANSI - Higher level macros |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Requirements |
||||
|
||||
package require textutil::repeat |
||||
package require textutil::tabify |
||||
package require term::ansi::code::ctrl |
||||
|
||||
namespace eval ::term::ansi::code::macros {} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## API. Symbolic names. |
||||
|
||||
proc ::term::ansi::code::macros::import {{ns macros} args} { |
||||
if {![llength $args]} {set args *} |
||||
set args ::term::ansi::code::macros::[join $args " ::term::ansi::code::macros::"] |
||||
uplevel 1 [list namespace eval ${ns} [linsert $args 0 namespace import]] |
||||
return |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Higher level operations |
||||
|
||||
# Format a menu / framed block of text |
||||
|
||||
proc ::term::ansi::code::macros::menu {menu} { |
||||
# Menu = dict (label => char) |
||||
array set _ {} |
||||
set shift 0 |
||||
foreach {label c} $menu { |
||||
if {[string first $c $label] < 0} { |
||||
set shift 1 |
||||
break |
||||
} |
||||
} |
||||
set max 0 |
||||
foreach {label c} $menu { |
||||
set pos [string first $c $label] |
||||
if {$shift || ($pos < 0)} { |
||||
set xlabel "$c $label" |
||||
set pos 0 |
||||
} else { |
||||
set xlabel $label |
||||
} |
||||
set len [string length $xlabel] |
||||
if {$len > $max} {set max $len} |
||||
set _($label) " [string replace $xlabel $pos $pos \ |
||||
[cd::sda_fgred][cd::sda_bold][string index $xlabel $pos][cd::sda_reset]]" |
||||
} |
||||
|
||||
append ms [cd::tlc][textutil::repeat::strRepeat [cd::hl] $max][cd::trc]\n |
||||
foreach {l c} $menu {append ms $_($l)\n} |
||||
append ms [cd::blc][textutil::repeat::strRepeat [cd::hl] $max][cd::brc] |
||||
|
||||
return [cd::groptim $ms] |
||||
} |
||||
|
||||
proc ::term::ansi::code::macros::frame {string} { |
||||
set lines [split [textutil::tabify::untabify2 $string] \n] |
||||
set max 0 |
||||
foreach l $lines { |
||||
if {[set len [string length $l]] > $max} {set max $len} |
||||
} |
||||
append fs [cd::tlc][textutil::repeat::strRepeat [cd::hl] $max][cd::trc]\n |
||||
foreach l $lines { |
||||
append fs [cd::vl]${l}[textutil::repeat::strRepeat " " [expr {$max-[string length $l]}]][cd::vl]\n |
||||
} |
||||
append fs [cd::blc][textutil::repeat::strRepeat [cd::hl] $max][cd::brc] |
||||
return [cd::groptim $fs] |
||||
} |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Data structures. |
||||
|
||||
namespace eval ::term::ansi::code::macros { |
||||
term::ansi::code::ctrl::import cd |
||||
|
||||
namespace export menu frame |
||||
} |
||||
|
||||
# ### ### ### ######### ######### ######### |
||||
## Ready |
||||
|
||||
package provide term::ansi::code::macros 0.1 |
||||
|
||||
## |
||||
# ### ### ### ######### ######### ######### |
Loading…
Reference in new issue