Browse Source

late checkin punk::char punk::console etc etc

master
Julian Noble 11 months ago
parent
commit
aa3d3fc56a
  1. 28
      src/modules/flagfilter-0.3.tm
  2. 235
      src/modules/patternpunk-1.1.tm
  3. 75
      src/modules/punk-0.1.tm
  4. 218
      src/modules/punk/cap-999999.0a1.0.tm
  5. 3
      src/modules/punk/cap-buildversion.txt
  6. 996
      src/modules/punk/char-999999.0a1.0.tm
  7. 3
      src/modules/punk/char-buildversion.txt
  8. 3692
      src/modules/punk/char/glyph-list.txt
  9. 168
      src/modules/punk/console-999999.0a1.0.tm
  10. 3
      src/modules/punk/console-buildversion.txt
  11. 11
      src/modules/punk/mix-0.2.tm
  12. 79
      src/modules/punk/mix/base-0.1.tm
  13. 35
      src/modules/punk/mix/commandset/debug-999999.0a1.0.tm
  14. 83
      src/modules/punk/mix/commandset/layout-999999.0a1.0.tm
  15. 3
      src/modules/punk/mix/commandset/module-999999.0a1.0.tm
  16. 45
      src/modules/punk/mix/commandset/project-999999.0a1.0.tm
  17. 22
      src/modules/punk/mix/commandset/repo-999999.0a1.0.tm
  18. 38
      src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm
  19. 49
      src/modules/punk/mix/templates-999999.0a1.0.tm
  20. 3
      src/modules/punk/mix/templates-buildversion.txt
  21. 251
      src/modules/punk/mix/templates/layouts/project/src/make.tcl
  22. 1
      src/modules/punk/mix/util-999999.0a1.0.tm
  23. 41
      src/modules/punk/ns-999999.0a1.0.tm
  24. 12
      src/modules/punk/repl-0.1.tm
  25. 30
      src/modules/shellfilter-0.1.8.tm
  26. 92
      src/modules/shellrun-0.1.tm
  27. 80
      src/modules/textblock-999999.0a1.0.tm
  28. 73
      src/punk86.vfs/lib/app-shellspy/shellspy.tcl
  29. 632
      src/vendormodules/overtype-1.5.0.tm

28
src/modules/flagfilter-0.3.tm

@ -957,34 +957,6 @@ namespace eval flagfilter {
}
#!todo - check if -commandprocessors members will collide with existing -flags in values before moving them
#!todo - skip optional sub-flag value if the next arg following its parent is a flag i.e proper handling of -commandprocessors {cmd {cmd sub "default}} when only cmd supplied.
#!important to fix. At the moment it could eat a further unflagged item in values later in the list which was intended for a different -commandprocessors member!

235
src/modules/patternpunk-1.1.tm

@ -16,9 +16,9 @@ package require overtype
pattern::init
package provide patternpunk [namespace eval punk {
variable version
variable version
set version 1.1
set version 1.1
}]
@ -50,16 +50,16 @@ set ::punk::bannerTemplate {
}
>punk .. Method banner {args} {
set defaults [list -title "Pattern PUNK" -left "" -right ""]
if {[catch {set opts [dict merge $defaults $args]} ]} {
error "usage: banner \[-title \$title -left \$left -right \$right\]"
}
set defaults [list -title "Pattern PUNK" -left "" -right ""]
if {[catch {set opts [dict merge $defaults $args]} ]} {
error "usage: banner \[-title \$title -left \$left -right \$right\]"
}
set word1 [overtype::left [string repeat " " 9] [dict get $opts -left]]
set word2 [overtype::right [string repeat " " 7] [dict get $opts -right]]
set title [overtype::centre [string repeat " " 15] [dict get $opts -title]]
set word1 [overtype::left [string repeat " " 9] [dict get $opts -left]]
set word2 [overtype::right [string repeat " " 7] [dict get $opts -right]]
set title [overtype::centre [string repeat " " 15] [dict get $opts -title]]
return [string map [list 111111111 $word1 2222222 $word2 000000000000000 $title] $punk::bannerTemplate]
return [string map [list 111111111 $word1 2222222 $word2 000000000000000 $title] $punk::bannerTemplate]
}
@ -69,135 +69,166 @@ set ::punk::bannerTemplate {
>punk .. Property version $::punk::version
>punk .. Property front {
_|_
@ v @
~
- -
|_\ /_|
/ \
_+ +_
_|_
@ v @
~
- -
|_\ /_|
/ \
_+ +_
}
>punk .. Property back {
|
( | )
|
- -
|_\ /_|
/ \
_- -_
|
( | )
|
- -
|_\ /_|
/ \
_- -_
}
>punk .. Property rhs {
\\\_
\@ >
| ~
\_- -_
\\ /
/ \
_+ +_
\\\_
\@ >
| ~
\_- -_
\\ /
/ \
_+ +_
}
>punk .. Property right
>punk .. PropertyRead right {} {
return $o_rhs
return $o_rhs
}
>punk .. Property lhs {
_///
< @/
~ |
_- -_/
\ //
/ \
_+ +_
_///
< @/
~ |
_- -_/
\ //
/ \
_+ +_
}
>punk .. Property left
>punk .. PropertyRead left {} {
return $o_lhs
return $o_lhs
}
>punk .. Property rhs_air {
\\\_
\@ >
| ~
\_- -_/
\\
/ \
_+ +_
\\\_
\@ >
| ~
\_- -_/
\\
/ \
_+ +_
}
>punk .. Property lhs_air {
_///
< @/
~ |
\_- -_/
//
/ \
_+ +_
_///
< @/
~ |
\_- -_/
//
/ \
_+ +_
}
>punk .. Property lhs_hips {
_///
< @/
~ |
_- -_
\ | | /
/ \
_+ +_
_///
< @/
~ |
_- -_
\ | | /
/ \
_+ +_
}
>punk .. Property rhs_hips {
\\\_
\@ >
| ~
_- -_
\ | | /
/ \
_+ +_
\\\_
\@ >
| ~
_- -_
\ | | /
/ \
_+ +_
}
>punk .. Property piss {
\\\_
\@ >
| ~
\_- -_/
\\_ ..
/ \ ..
_+ +_ .
\\\_
\@ >
| ~
\_- -_/
\\_ ..
/ \ ..
_+ +_ .
}
>punk .. Property poop {
_///
< @/
^ |
_- -_
\ \\ /
//. ~
_+_+ @
_///
< @/
^ |
_- -_
\ \\ /
//. ~
_+_+ @
}
>punk .. Property fossil {
..
> <
\ / v
v \\_/
\/\\ v .
v_ /|\/ /
\__/
..
> <
\ / v
v \\_/
\/\\ v .
v_ /|\/ /
\__/
}
>punk .. Method gcross {{size 3}} {
if {$size % 2 == 0} {
incr size
}
package require punk::char
set x [punk::char::charshort boxd_ldc]
set bs [punk::char::charshort boxd_ldgullr]
set fs [punk::char::charshort boxd_ldgurll]
set armsize [expr {int(floor($size /2))}]
set row [lrepeat $size " "]
#toparm
for {set i 0} {$i < $armsize} {incr i} {
set r $row
lset r $i $bs
lset r end-$i $fs
append out [join $r ""] \n
}
set r $row
lset r $armsize $x
append out [join $r ""] \n
for {set i [expr {$armsize -1}]} {$i >= 0} {incr i -1} {
set r $row
lset r $i $fs
lset r end-$i $bs
append out [join $r ""] \n
}
return $out
}
>punk .. Method dumpProperties {{object ::>punk}} {
foreach {p v} [$object .. Properties . pairs] {
puts $p
puts [set $v]
puts \n
}
foreach {p v} [$object .. Properties . pairs] {
puts $p
puts [set $v]
puts \n
}
}
>punk .. Method listProperties {{object ::>punk}} {
set result {}
foreach {p v} [$object .. Properties . pairs] {
lappend result $p [set $v]
}
return $result
set result {}
foreach {p v} [$object .. Properties . pairs] {
lappend result $p [set $v]
}
return $result
}
@ -224,10 +255,10 @@ proc ->> {filename string} {
#presumably this is to allow calling of standard objects using dotted notation?
::>pattern .. Create ::>
::> .. Method item {args} {
#uplevel #0 $args
#uplevel #0 [join $args]
#uplevel #0 $args
#uplevel #0 [join $args]
uplevel #0 $args
uplevel #0 $args
}
::> .. DefaultMethod item

75
src/modules/punk-0.1.tm

@ -122,9 +122,27 @@ namespace eval punk {
variable last_run_display [list]
variable colour_disabled 0
# https://no-color.org
if {[info exists ::env(NO_COLOR)]} {
if {$::env(NO_COLOR) ne ""} {
set colour_disabled 1
}
}
#variable re_headvar1 {([a-zA-Z:@.(),]+?)(?![^(]*\))(,.*)*$}
#load package and move to namespace of same name
proc pkguse {pkg} {
set ver [package require $pkg]
if {[namespace exists ::$pkg]} {
set out [punk::ns::ns/ / ::$pkg]
append out \n $ver
return $out
} else {
set out $ver
}
return $out
}
interp alias "" use "" punk::pkguse
#-----------------------------------------------------------------------------------
#strlen is important for testing issues with string representationa and shimmering.
#This specific implementation with append (as at 2023-09) is designed to ensure the original str representation isn't changed
@ -139,6 +157,7 @@ namespace eval punk {
append obj2 $obj {}
}
interp alias "" strlen "" ::punk::strlen
interp alias "" str_len "" ::punk::strlen
interp alias "" objclone "" ::punk::objclone
#proc ::strlen {str} {
# string length [append str2 $str {}]
@ -147,6 +166,17 @@ namespace eval punk {
# append obj2 $obj {}
#}
#-----------------------------------------------------------------------------------
#order of arguments designed for pipelining
#review - 'piper_' prefix is a naming convention for functions that are ordered for tail-argument pipelining
#piper_ function names should read intuitively when used in a pipeline with tail argument supplied by the pipeline - but may seem reversed when using standalone.
proc piper_append {new base} {
append base $new
}
interp alias "" piper_append "" ::punk::piper_append
proc piper_prepend {new base} {
append new $base
}
interp alias "" piper_prepend "" ::punk::piper_prepend
proc ::punk::K {x y} { return $x}
@ -5592,12 +5622,19 @@ namespace eval punk {
if {[file type $path] eq "file"} {
set tcl_extensions [list ".tcl" ".tm" ".kit" ".tk"] ;#todo - load from config
if {[string tolower [file extension $path]] in $tcl_extensions} {
set py_extensions [list ".py"]
set ext [file extension $path]
set extlower [string tolower $ext]
if {$extlower in $tcl_extensions} {
set newargs $atail
set ::argv0 $path
set ::argc [llength $newargs]
set ::argv $newargs
tailcall source $path
} elseif {$extlower in $py_extensions} {
set newargs $atail
set pycmd [auto_execok python]
tailcall {*}$pycmd $path {*}$newargs
} else {
set fd [open $path r]
set chunk [read $fd 4000]; close $fd
@ -6187,6 +6224,40 @@ namespace eval punk {
}
}
}
#
proc print_dict {d args} {
set defaults [dict create\
-channel ""\
-pattern *\
-cols 1\
]
set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- ---
set pattern [dict get $opts -pattern]
set channel [dict get $opts -channel]
set cols [dict get $opts -cols]
# -- --- --- --- --- --- --- --- --- ---
set out ""
set filtered_keys [lsort -dictionary [dict keys $d $pattern]]
if {[llength $filtered_keys]} {
set i 1
set maxl [::tcl::mathfunc::max {*}[lmap v $filtered_keys {string length $v}]]
foreach key $filtered_keys {
append out [format "%-*s %s " $maxl $key [dict get $d $key]]
if {$i % $cols == 0} {
set out [string range $out 0 end-1]
append out \n
}
incr i
}
}
if {$channel eq ""} {
return $out
} else {
puts $channel $out
}
}
proc ooinspect {obj} {

218
src/modules/punk/cap-999999.0a1.0.tm

@ -0,0 +1,218 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application punk::cap 999999.0a1.0
# Meta platform tcl
# Meta description pkg capability register
# Meta license BSD
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::cap {
variable pkgcap [dict create]
variable caps [dict create]
proc register_package {pkg capabilitylist} {
variable pkgcap
variable caps
if {[string match ::* $pkg]} {
set pkg [string range $pkg 2 end]
}
#for each capability
# - ensure 1st element is a single word
# - ensure that if 2nd element (capdict) is present - it is dict shaped
foreach c $capabilitylist {
lassign $c capname capdict
if {[llength $capname] !=1} {
error "register_package error. pkg: '$pkg' An entry in the capability list doesn't appear to have a single-word name. Problematic entry:'$c'"
}
if {[expr {[llength $capdict] %2 != 0}]} {
error "register_package error. pkg:'$pkg' The second element for capname:'$capname' doesn't appear to be a valid dict. Problematic entry: '$c'"
}
if {[dict exists $caps $capname]} {
set cap_pkgs [dict get $caps $capname]
} else {
set cap_pkgs [list]
}
if {$pkg ni $cap_pkgs} {
dict lappend caps $capname $pkg
}
}
dict set pkgcap $pkg $capabilitylist
}
proc promote_package {pkg} {
variable pkgcap
variable caps
if {[string match ::* $pkg]} {
set pkg [string range $pkg 2 end]
}
if {![dict exists $pkgcap $pkg]} {
error "punk::cap::promote_package error pkg'$pkg' not registered. Use register_package \$pkg first"
}
if {[dict size $pkgcap] > 1} {
set pkginfo [dict get $pkgcap $pkg]
#remove and re-add at end of dict
dict unset pkgcap $pkg
dict set pkgcap $pkg $pkginfo
foreach {cap cap_pkgs} $caps {
if {$pkg in $cap_pkgs} {
set posn [lsearch $cap_pkgs $pkg]
if {$posn >=0} {
#rewrite package list with pkg at tail of list for this capability
set cap_pkgs [lreplace $cap_pkgs $posn $posn]
lappend cap_pkgs $pkg
dict set caps $cap $cap_pkgs
}
}
}
}
}
proc demote_package {pkg} {
variable pkgcap
variable caps
if {[string match ::* $pkg]} {
set pkg [string range $pkg 2 end]
}
if {![dict exists $pkgcap $pkg]} {
error "punk::cap::promote_package error pkg'$pkg' not registered. Use register_package \$pkg first"
}
if {[dict size $pkgcap] > 1} {
set pkginfo [dict get $pkgcap $pkg]
#remove and re-add at start of dict
dict unset pkgcap $pkg
dict set pkgcap $pkg $pkginfo
set pkgcap [dict merge [dict create $pkg $pkginfo] $pkgcap]
foreach {cap cap_pkgs} $caps {
if {$pkg in $cap_pkgs} {
set posn [lsearch $cap_pkgs $pkg]
if {$posn >=0} {
#rewrite package list with pkg at head of list for this capability
set cap_pkgs [lreplace $cap_pkgs $posn $posn]
set cap_pkgs [list $pkg {*}$cap_pkgs]
dict set caps $cap $cap_pkgs
}
}
}
}
}
proc unregister_package {pkg} {
variable pkgcap
variable caps
if {[string match ::* $pkg]} {
set pkg [string range $pkg 2 end]
}
if {[dict exists $pkgcap $pkg]} {
#remove corresponding entries in caps
set capabilitylist [dict get $pkgcap $pkg]
foreach c $capabilitylist {
lassign $c capname _capdict
set pkglist [dict get $caps $capname]
set posn [lsearch $pkglist $pkg]
if {$posn >= 0} {
set pkglist [lreplace $pkglist $posn $posn]
dict set caps $capname $pkglist
}
}
#delete the main registration record
dict unset pkgcap $pkg
}
}
proc registered_package {pkg} {
variable pkgcap
if {[string match ::* $pkg]} {
set pkg [string range $pkg 2 end]
}
if {[dict exists $pkgcap $pkg]} {
return [dict get $pkgcap $pkg]
} else {
return
}
}
proc registered_packages {} {
variable pkgcap
return $pkgcap
}
proc capabilities {{glob *}} {
variable caps
set keys [lsort [dict keys $caps $glob]]
set cap_list [list]
foreach k $keys {
lappend cap_list [list $k [dict get $caps $k]]
}
return $cap_list
}
namespace eval templates {
#return a dict keyed on folder with source pkg as value
proc folders {} {
package require punk::cap
set caplist [punk::cap::capabilities templates]
# e.g {templates {punk::mix::templates ::somepkg}}
set templates_record [lindex $caplist 0]
set pkgs [lindex $templates_record 1]
set folderdict [dict create]
foreach pkg $pkgs {
set caplist [punk::cap::registered_package $pkg]
set templates_entries [lsearch -all -inline -index 0 $caplist templates] ;#we generally expect only one - but if multiple exist - use them
foreach templates_info $templates_entries {
lassign $templates_info _templates templates_dict
if {[dict exists $templates_dict relpath]} {
set provide_statement [package ifneeded $pkg [package require $pkg]]
set tmfile [lindex $provide_statement end]
#set tmdir [file dirname [lindex $provide_statement end]]
set tpath [file normalize [file join $tmfile [dict get $templates_dict relpath]]] ;#relpath is relative to the tm *file* - not it's containing folder
#relpath relative to file is important for tm files that are zip/tar based containers
if {[file isdirectory $tpath]} {
dict set folderdict $tpath [list source $pkg sourcetype package]
} else {
puts stderr "punk::mix template_folders WARNING - unable to determine base folder for package '$pkg' which is registered with punk::mix as a provider of 'templates' capability"
}
} else {
puts stderr "punk::mix template_folders WARNING - registered pkg 'pkg' has capability 'templates' but no 'relpath' key - unable to use as source of templates"
}
}
}
return $folderdict
}
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::cap [namespace eval punk::cap {
variable version
set version 999999.0a1.0
}]
return

3
src/modules/punk/cap-buildversion.txt

@ -0,0 +1,3 @@
0.1.0
#First line must be a semantic version number
#all other lines are ignored.

996
src/modules/punk/char-999999.0a1.0.tm

@ -0,0 +1,996 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application punk::char 999999.0a1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
package require mime
package require overtype
#Note that ansi escapes can begin with \033\[ (\u001b\[) or the single character "Control Sequence Introducer" 0x9b
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::char {
namespace export *
variable invalid "???" ;# ideally this would be 0xFFFD - which should display as black diamond/rhombus with question mark. As at 2023 - this tends to display indistinguishably from other missing glyph boxes - so we use a longer sequence we can detect by length and to display obviously
variable invalid_display_char \u25ab; #todo - change to 0xFFFD once diamond glyph more common?
#just the 7-bit ascii. use [page ascii] for the 8-bit
proc ascii {} {return {
00 NUL 01 SOH 02 STX 03 ETX 04 EOT 05 ENQ 06 ACK 07 BEL
08 BS 09 HT 0a LF 0b VT 0c FF 0d CR 0e SO 0f SI
10 DLE 11 DC1 12 DC2 13 DC3 14 DC4 15 NAK 16 SYN 17 ETB
18 CAN 19 EM 1a SUB 1b ESC 1c FS 1d GS 1e RS 1f US
20 SP 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f DEL
}}
#G0 character set
proc ascii2 {} {
set dict [asciidict2]
set out ""
set i 1
append out " "
dict for {k v} $dict {
#single chars are wrapped with \033(0 and \033(B ie total length 7
if {[string length $v] == 7} {
set v " $v "
} elseif {[string length $v] == 2} {
set v "$v "
} elseif {[string length $v] == 0} {
set v " "
}
append out "$k $v "
if {$i > 0 && $i % 8 == 0} {
set out [string range $out 0 end-2]
append out \n " "
}
incr i
}
set out [string trimright $out " "]
return $out
}
proc symbol {} {
tailcall page symbol
}
proc dingbats {} {
set out ""
append out [page dingbats] \n
set unicode_dict [charset_dict dingbats]
append out " "
set i 1
dict for {k charinfo} $unicode_dict {
set char [dict get $charinfo char]
if {[string length $char] == 0} {
set displayv " "
} elseif {[string length $char] == 1} {
set displayv " $char "
} else {
set displayv $char
}
append out "$k $displayv "
if {$i > 0 && $i % 8 == 0} {
set out [string range $out 0 end-2]
append out \n " "
}
incr i
}
return $out
}
proc page_names {{search *}} {
set all_names [list]
set d [page_names_dict $search]
dict for {k v} $d {
if {$k ni $all_names} {
lappend all_names $k
}
foreach m $v {
if {$m ni $all_names} {
lappend all_names $m
}
}
}
return [lsort $all_names]
}
proc page_names_help {{namesearch *}} {
set d [page_names_dict $namesearch]
set out ""
dict for {k v} $d {
append out "$k $v" \n
}
return [linesort $out]
}
proc page_names_dict {{search *}} {
if {![regexp {[?*]} $search]} {
set search "*$search*"
}
set encnames [encoding names]
foreach enc $encnames {
dict set d $enc [list]
}
set mimenames [array get ::mime::reversemap]
dict for {mname encname} $mimenames {
if {$encname in $encnames} {
set enclist [dict get $d $encname]
if {$mname ni $enclist} {
dict lappend d $encname $mname
}
}
}
foreach enc [lsort $encnames] {
set mime_enc [mime::mapencoding $enc]
if {$mime_enc ne ""} {
set enclist [dict get $d $enc]
if {$mime_enc ni $enclist} {
dict lappend d $enc $mime_enc
}
}
}
set dresult [dict create]
if {$search ne "*"} {
dict for {k v} $d {
if {[string match -nocase $search $k] || ([lsearch -nocase $v $search]) >= 0} {
dict set dresult $k $v
}
}
} else {
set dresult $d
}
return $dresult
}
proc page8 {encname args} {
dict set args -cols 8
tailcall page $encname {*}$args
}
proc page16 {encname args} {
dict set args -cols 16
tailcall page $encname {*}$args
}
proc page {encname args} {
variable invalid
set encname [encname $encname]
set defaults [list\
-range {0 256}\
-cols 8\
]
set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- ---
set cols [dict get $opts -cols]
# -- --- --- --- --- --- --- --- ---
set d_bytedisplay [basedict_display]
#set d_ascii [pagedict_raw ascii]
set d_ascii [basedict]
set d_asciiposn [lreverse $d_ascii] ;#values should be unique except for "???". We are mainly interested in which ones have display-names e.g cr csi
#The results of this are best seen by comparing the ebcdic and ascii pages
set d_page [pagedict_raw $encname]
set out ""
set i 1
append out " "
dict for {k rawchar} $d_page {
set num [expr {"0x$k"}]
#see if ascii equivalent exists and has a name
if {$rawchar eq $invalid} {
set displayv "$invalid"
} else {
set bytedisplay ""
if {[dict exists $d_asciiposn $rawchar]} {
set asciiposn [dict get $d_asciiposn $rawchar]
set bytedisplay [dict get $d_bytedisplay $asciiposn]
}
if {$bytedisplay eq $invalid} {
#
set displayv " $rawchar "
} else {
set displaylen [string length $bytedisplay]
if {$displaylen == 2} {
set displayv "$bytedisplay "
} elseif {$displaylen == 3} {
set displayv $bytedisplay
} else {
if {[string length $rawchar] == 0} {
set displayv " "
} else {
#presumed 1
set displayv " $rawchar "
}
}
}
}
append out "$k $displayv "
if {$i > 0 && $i % $cols == 0} {
set out [string range $out 0 end-2]
append out \n " "
}
incr i
}
set out [string trimright $out " "]
return $out
}
proc pagechar1 {page num} {
set encpage $page
if {$page ni [encoding names]} {
set mimenamesdict [page_names_dict]
dict for {k v} $mimenamesdict {
if {[lsearch -nocase $v $page] >= 0} {
set encpage $k
break
}
}
if {$encpage eq $page} {
#didn't find a mapping to something else..
error "unknown encoding '$page'"
}
}
encoding convertfrom $encpage [format %c $num]
}
proc pagechar {page num} {
set encpage $page
if {$page ni [encoding names]} {
set mimenamesdict [page_names_dict]
dict for {k v} $mimenamesdict {
if {[lsearch -nocase $v $page] >= 0} {
set encpage $k
break
}
}
if {$encpage eq $page} {
#didn't find a mapping to something else..
error "unknown encoding '$page'"
}
}
set ch [format %c $num]
if {[decodable $ch $encpage]} {
set outchar [encoding convertfrom $encpage $ch]
} else {
#here we will use \0xFFFD instead of our replacement string ??? - as the name pagechar implies always returning a single character. REVIEW.
set outchar $::punk::char::invalid_display_char
}
return $outchar
}
proc pagebyte {page num} {
set encpage $page
if {$page ni [encoding names]} {
set mimenamesdict [page_names_dict]
dict for {k v} $mimenamesdict {
if {[lsearch -nocase $v $page] >= 0} {
set encpage $k
break
}
}
if {$encpage eq $page} {
#didn't find a mapping to something else..
error "unknown encoding '$page'"
}
}
set ch [format %c $num]
if {[decodable $ch $encpage]} {
#set outchar [encoding convertto $encpage [format %c $num]]
set outchar [format %c $num]
} else {
set outchar $::punk::char::invalid_display_char
}
return $outchar
}
proc all_pages {} {
set out ""
set mimenamesdict [page_names_dict]
foreach encname [encoding names] {
if {[dict exists $mimenamesdict $encname]} {
set alt "([dict get $mimenamesdict $encname])"
} else {
set alt ""
}
append out "$encname $alt" \n
append out [page $encname]
}
return $out
}
proc encname {encoding_name_or_alias} {
set encname $encoding_name_or_alias
if {[lsearch -nocase [page_names] $encname] < 0} {
error "Unknown encoding '$encname' - use 'punk::char::page_names' to see valid encoding names on this system"
}
if {$encname ni [encoding names]} {
set encname [mime::reversemapencoding $encname]
}
return $encname
}
proc pagedict_raw {encname} {
variable invalid ;# ="???"
set encname [encname $encname]
set d [dict create]
for {set i 0} {$i < 256} {incr i} {
set k [format %02x $i]
#dict set d $k [encoding convertfrom $encname [format %c $i]]
set ch [format %c $i] ;
#jmn
if {[decodable $ch $encname]} {
#set encchar [encoding convertto $encname $ch]
#dict set d $k [encoding convertfrom $encchar]
dict set d $k [encoding convertfrom $encname $ch]
} else {
dict set d $k $invalid ;#use replacement so we can detect difference from actual "?"
}
}
return $d
}
proc asciidict {} {
variable invalid
set d [dict create]
set a128 [asciidict128]
for {set i 0} {$i < 256} {incr i} {
set k [format %02x $i]
if {$i <= 127} {
dict set d $k [dict get $a128 $k]
} else {
#
dict set d $k $invalid
}
if {$i <=32} {
#no point emitting the lower control characters raw to screen - emit the short-names defined in the 'ascii' proc
dict set d $k [dict get $a128 $k]
} else {
if {$i == 0x9b} {
dict set d $k CSI ;#don't emit the ansi 'Control Sequence Introducer' - or it will be interpreted by the console and affect the layout.
} else {
dict set d $k [format %c $i]
}
}
}
return $d
}
proc basedict_display {} {
set d [dict create]
set a128 [asciidict128]
for {set i 0} {$i < 256} {incr i} {
set k [format %02x $i]
if {$i <=32} {
#no point emitting the lower control characters raw to screen - emit the short-names defined in the 'ascii' proc
dict set d $k [dict get $a128 $k]
} else {
if {$i == 0x9b} {
dict set d $k CSI ;#don't emit the ansi 'Control Sequence Introducer' - or it will be interpreted by the console and affect the layout.
} else {
#dict set d $k [encoding convertfrom [encoding system] [format %c $i]]
#don't use encoding convertfrom - we want the value independent of the current encoding system.
dict set d $k [format %c $i]
}
}
}
return $d
}
proc basedict_encoding_system {} {
#result depends on 'encoding system' currently in effect
set d [dict create]
for {set i 0} {$i < 256} {incr i} {
set k [format %02x $i]
dict set d $k [encoding convertfrom [encoding system] [format %c $i]]
}
return $d
}
proc basedict {} {
#this gives same result independent of current value of 'encoding system'
set d [dict create]
for {set i 0} {$i < 256} {incr i} {
set k [format %02x $i]
dict set d $k [format %c $i]
}
return $d
}
proc pagedict {pagename args} {
variable charsets
set encname [encname $pagename]
set defaults [list\
-range {0 256}\
-charset ""\
]
set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- ---
set range [dict get $opts -range]
set charset [dict get $opts -charset]
# -- --- --- --- --- --- --- --- --- ---
if {$charset ne ""} {
if {$charset ni [charset_names]} {
error "unknown charset '$charset' - use 'charset_names' to get list"
}
set setinfo [dict get $charsets $charset]
set ranges [dict get $setinfo ranges]
set charset_dict [dict create]
foreach r $ranges {
set start [dict get $r start]
set end [dict get $r end]
#set charset_dict [dict merge $charset_dict [char_range_dict $start $end]]
break
}
} else {
set start [lindex $range 0]
set end [lindex $range 1]
}
set d [dict create]
for {set i $start} {$i <= $end} {incr i} {
set k [format %02x $i]
dict set d $k [encoding convertfrom $encname [format %c $i]]
}
return $d
}
#todo - benchmark peformance - improve punk pipeline
proc asciidict128 {} {
regexp -all -inline {\S+} [concat {*}[linelist [ascii] -line trimleft]]
}
proc _asciidict128 {} {
.= ascii |> .=>1 linelist -line trimleft |> .=* concat |> {regexp -all -inline {\S+} $data}
}
proc asciidict2 {} {
set d [dict create]
dict for {k v} [basedict_display] {
if {[string length $v] == 1} {
set num [expr {"0x$k"}]
#dict set d $k "\033(0[subst \\u00$k]\033(B"
dict set d $k "\033(0[format %c $num]\033(B"
} else {
dict set d $k $v
}
}
return $d
}
#-- --- --- --- --- --- --- ---
# encoding convertfrom & encoding convertto can be somewhat confusing to think about. (Need to think in reverse.)
# e.g encoding convertto dingbats <somethingpretty> will output something that doesn't look dingbatty on screen.
#-- --- --- --- --- --- --- ---
#must use Tcl instead of tcl (at least for 8.6)
if {![package vsatisfies [package present Tcl] 8.7]} {
proc encodable "s {enc [encoding system]}" {
set encname [encname $enc]
if {($encname eq "ascii")} {
#8.6 fails to round-trip convert 0x7f del character despite it being in the ascii range (review Why?? what else doesn't round-trip but should?)
#just strip it out of the string as we are only after a boolean answer and if s is only a single del char empty string will result in true
set s [string map [list [format %c 0x7f] ""] $s]
}
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]]
}
proc decodable "s {enc [encoding system]}" {
set encname [encname $enc]
#review
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
}
} else {
proc encodable "s {enc [encoding system]}" {
set encname [encname $enc]
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]]
}
proc decodable "s {enc [encoding system]}" {
set encname [encname $enc]
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
}
}
#-- --- --- --- --- --- --- ---
proc test_japanese {{encoding jis0208}} {
#A very basic test of 2char encodings such as jis0208
set yatbun 日本 ;# encoding convertfrom jis0208 F|K\\
lassign [split $yatbun] yat bun
puts "original yatbun ${yat} ${bun}"
set eyat [encoding convertto $encoding $yat]
set ebun [encoding convertto $encoding $bun]
puts "$encoding encoded: ${eyat} ${ebun}"
puts "reencoded: [encoding convertfrom $encoding $eyat] [encoding convertfrom $encoding $ebun]"
return $yatbun
}
#G0 Sets Sequence G1 Sets Sequence Meaning
#ESC ( A ESC ) A United Kingdom Set
#ESC ( B ESC ) B ASCII Set
#ESC ( 0 ESC ) 0 Special Graphics
#ESC ( 1 ESC ) 1 Alternate Character ROM Standard Character Set
#ESC ( 2 ESC ) 2 Alternate Character ROM Special Graphic
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# Unicode character sets - some hardcoded - some loadable from data files
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
variable charinfo [dict create]
variable charsets [dict create]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# Aggregate character sets (ones that pick various ranges from underlying unicode ranges)
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "WGL4" [list altname "Windows Glyph List 4" ranges [list\
{start 0 end 127 name "basic latin"}\
{start 128 end 255 name "latin-1 supplement"}\
] description "Greek and Coptic"]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#The base page 0-256 8bit range - values don't have specific characters or descriptions - as they are codepage dependent
#we will fill this here for completeness - but with placeholders
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "non-unicode" [list ranges [list {start 0 end 127 name ascii} {start 128 end 255 name 8bit-ascii}] description "8bit extended ascii range"]
for {set i 0} {$i < 256} {incr i} {
dict set charinfo $i [list desc "codepage-dependent" short "byte_[format %02x $i]"]
}
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# Unicode ranges
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "greek" [list ranges [list {start 880 end 1023} {start 7936 end 8191}] description "Greek and Coptic"]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "latin_extended_additional" [list ranges [list {start 7680 end 7935 }] description "Latin Extended Additional"]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "block_elements" [list ranges [list {start 9600 end 9631}] description "Block Elements"]
dict set charinfo 9600 [list desc "Upper Half Block" short "blocke_up_half"]
dict set charinfo 9601 [list desc "Lower One Eighth Block" short "blocke_lw_1_8th"]
dict set charinfo 9602 [list desc "Lower One Quarter Block" short "blocke_lw_1_qtr"]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "dingbats" [list ranges [list {start 9984 end 10175 }] description "Unicode Dingbats"]
dict set charinfo 9984 [list desc "Black Safety Scissors" short "dingbats_black_safety_scissors"]
#...
dict set charinfo 10175 [list desc "Double Curly Loop" short "dingbats_double_curly_loop"]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# emoticons https://www.unicode.org/charts/PDF/U1F600.pdf
dict set charsets "emoticons" [list ranges [list {start 128512 end 128591}] description "Emoticons"]
dict set charinfo 128512 [list desc "Grinning Face" short "emoticon_grinface"]
dict set charinfo 128513 [list desc "Grinning Face with Smiling Eyes" short "emoticon_grinface_smile_eyes"]
dict set charinfo 128514 [list desc "Face with Tears of Joy" short "emoticon_face_tears_joy"]
#todo
dict set charinfo 128590 [list desc "Person with Pouting Face" short "emoticon_person_pouting"]
# -- -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
dict set charsets "box_drawing" [list ranges [list {start 9472 end 9599}] description "Box Drawing"]
dict set charinfo 9472 [list desc "Box Drawings Light Horizontal" short "boxd_lhz"]
dict set charinfo 9473 [list desc "Box Drawings Heavy Horizontal" short "boxd_hhz"]
dict set charinfo 9474 [list desc "Box Drawings Light Vertical" short "boxd_lv"]
dict set charinfo 9475 [list desc "Box Drawings Heavy Vertical" short "boxd_hv"]
dict set charinfo 9476 [list desc "Box Drawings Light Triple Dash Horizontal" short "boxd_ltdshhz"]
dict set charinfo 9477 [list desc "Box Drawings Heavy Triple Dash Horizontal" short "boxd_htdshhz"]
dict set charinfo 9478 [list desc "Box Drawings Light Triple Dash Vertical" short "boxd_ltdshv"]
dict set charinfo 9479 [list desc "Box Drawings Heavy Triple Dash Vertical" short "boxd_htdshv"]
dict set charinfo 9480 [list desc "Box Drawings Light Quadruple Dash Horizontal" short "boxd_lqdshhz"]
dict set charinfo 9481 [list desc "Box Drawings Heavy Quadruple Dash Horizontal" short "boxd_hqdshhz"]
dict set charinfo 9482 [list desc "Box Drawings Light Quadruple Dash Vertical" short "boxd_lqdshv"]
dict set charinfo 9483 [list desc "Box Drawings Heavy Quadruple Dash Vertical" short "boxd_hqdshv"]
dict set charinfo 9484 [list desc "Box Drawings Light Down and Right" short "boxd_ldr"]
dict set charinfo 9485 [list desc "Box Drawings Down Light and Right Heavy" short "boxd_dlrh"]
dict set charinfo 9486 [list desc "Box Drawings Down Heavy and Right Light" short "boxd_dhrl"]
dict set charinfo 9487 [list desc "Box Drawings Heavy Down and Right" short "boxd_hdr"]
dict set charinfo 9488 [list desc "Box Drawings Light Down and Left" short "boxd_ldl"]
dict set charinfo 9489 [list desc "Box Drawings Down Light and Left Heavy" short "boxd_dllh"]
dict set charinfo 9490 [list desc "Box Drawings Down Heavy and Left Light" short "boxd_dhll"]
dict set charinfo 9491 [list desc "Box Drawings Heavy Down and Left" short "boxd_hdl"]
dict set charinfo 9492 [list desc "Box Drawings Light Up and Right" short "boxd_lur"]
dict set charinfo 9493 [list desc "Box Drawings Up Light and Right Heavy" short "boxd_ulrh"]
dict set charinfo 9494 [list desc "Box Drawings Up Heavy and Right Light" short "boxd_uhrl"]
dict set charinfo 9495 [list desc "Box Drawings Heavy Up and Right" short "boxd_hur"]
dict set charinfo 9496 [list desc "Box Drawings Light Up and Left" short "boxd_lul"]
dict set charinfo 9497 [list desc "Box Drawings Up Light and Left Heavy" short "boxd_ullh"]
dict set charinfo 9498 [list desc "Box Drawings Up Heavy and Left Light" short "boxd_uhll"]
dict set charinfo 9499 [list desc "Box Drawings Heavy Up and Left" short "boxd_hul"]
dict set charinfo 9500 [list desc "Box Drawings Light Vertical and Right" short "boxd_lvr"]
dict set charinfo 9501 [list desc "Box Drawings Vertical Light and Right Heavy" short "boxd_vlrh"]
dict set charinfo 9502 [list desc "Box Drawings Up Heavy and Right Down Light" short "boxd_uhrdl"]
dict set charinfo 9503 [list desc "Box Drawings Down Heavy and Right Up Light" short "boxd_dhrul"]
dict set charinfo 9504 [list desc "Box Drawings Vertical Heavy and Right Light" short "boxd_vhrl"]
dict set charinfo 9505 [list desc "Box Drawings Down Light and Right Up Heavy" short "boxd_dlruh"]
dict set charinfo 9506 [list desc "Box Drawings Up Light and Right Down Heavy" short "boxd_ulrdh"]
dict set charinfo 9507 [list desc "Box Drawings Heavy Vertical and Right" short "boxd_hvr"]
dict set charinfo 9508 [list desc "Box Drawings Light Vertical and Left" short "boxd_lvl"]
dict set charinfo 9509 [list desc "Box Drawings Vertical Light and Left Heavy" short "boxd_vllh"]
dict set charinfo 9510 [list desc "Box Drawings Up Heavy and Let Down Light" short "boxd_uhldl"]
dict set charinfo 9511 [list desc "Box Drawings Down Heavy and Left Up Light" short "boxd_dhlul"]
dict set charinfo 9512 [list desc "Box Drawings Vertical Heavy and Left Light" short "boxd_vhll"]
dict set charinfo 9513 [list desc "Box Drawings Down Light and left Up Heavy" short "boxd_dlluh"]
dict set charinfo 9514 [list desc "Box Drawings Up Light and Left Down Heavy" short "boxd_ulldh"]
dict set charinfo 9515 [list desc "Box Drawings Heavy Vertical and Left" short "boxd_hvl"]
dict set charinfo 9516 [list desc "Box Drawings Light Down and Horizontal" short "boxd_ldhz"]
dict set charinfo 9517 [list desc "Box Drawings Left Heavy and Right Down Light" short "boxd_lhrdl"]
dict set charinfo 9518 [list desc "Box Drawings Right Heavy and Left Down Light" short "boxd_rhldl"]
dict set charinfo 9519 [list desc "Box Drawings Down Light and Horizontal Heavy" short "boxd_dlhzh"]
dict set charinfo 9520 [list desc "Box Drawings Down Heavy and Horizontal Light" short "boxd_dhhzl"]
dict set charinfo 9521 [list desc "Box Drawings Right Light and Left Down Heavy" short "boxd_rlldh"]
dict set charinfo 9522 [list desc "Box Drawings Left Light and Right Down Heavy" short "boxd_llrdh"]
dict set charinfo 9523 [list desc "Box Drawings Heavy Down and Horizontal" short "boxd_hdhz"]
dict set charinfo 9524 [list desc "Box Drawings Light Up and Horizontal" short "boxd_luhz"]
dict set charinfo 9525 [list desc "Box Drawings Left Heavy and Right Up Light" short "boxd_lhrul"]
dict set charinfo 9526 [list desc "Box Drawings Right Heavy and Left Up Light" short "boxd_rhlul"]
dict set charinfo 9527 [list desc "Box Drawings Up Light and Horizontal Heavy" short "boxd_ulhzh"]
dict set charinfo 9528 [list desc "Box Drawings Up Heavy and Horizontal Light" short "boxd_uhhzl"]
dict set charinfo 9529 [list desc "Box Drawings Right Light and Left Up Heavy" short "boxd_rlluh"]
dict set charinfo 9530 [list desc "Box Drawings Left Light and Right Up Heavy" short "boxd_llruh"]
dict set charinfo 9531 [list desc "Box Drawings Heavy Up and Horizontal" short "boxd_huhz"]
dict set charinfo 9532 [list desc "Box Drawings Light Vertical and Horizontal" short "boxd_lvhz"]
dict set charinfo 9533 [list desc "Box Drawings Left Heavy and Right Vertical Light" short "boxd_lhrvl"]
dict set charinfo 9534 [list desc "Box Drawings Right Heavy and Left Vertical Light" short "boxd_rhlvl"]
dict set charinfo 9535 [list desc "Box Drawings Vertical Light and Horizontal Heavy" short "boxd_vlhzh"]
dict set charinfo 9536 [list desc "Box Drawings Up Heavy and Down Horizontal Light" short "boxd_uhdhzl"]
dict set charinfo 9537 [list desc "Box Drawings Down Heavy and Up Horizontal Light" short "boxd_dhuhzl"]
dict set charinfo 9538 [list desc "Box Drawings Vertical Heavy and Horizontal Light" short "boxd_vhhzl"]
dict set charinfo 9539 [list desc "Box Drawings Left Up Heavy and Right Down Light" short "boxd_luhrdl"]
dict set charinfo 9540 [list desc "Box Drawings Right Up Heavy and Left Down Light" short "boxd_ruhldl"]
dict set charinfo 9541 [list desc "Box Drawings Left Down Heavy and Right Up Light" short "boxd_ldhrul"]
dict set charinfo 9542 [list desc "Box Drawings Right Down Heavy and Left Up Light" short "boxd_rdhlul"]
dict set charinfo 9543 [list desc "Box Drawings Down Light and Up Horizontal Heavy" short "boxd_dluhzh"]
dict set charinfo 9544 [list desc "Box Drawings Up Light and Down Horizontal Heavy" short "boxd_dldhzh"]
dict set charinfo 9545 [list desc "Box Drawings Right Light and Left Vertical Heavy" short "boxd_rllvh"]
dict set charinfo 9546 [list desc "Box Drawings Left Light and Right Vertical Heavy" short "boxd_llrvh"]
dict set charinfo 9547 [list desc "Box Drawings Heavy Vertical and Horizontal" short "boxd_hvhz"]
dict set charinfo 9548 [list desc "Box Drawings Light Double Dash Horizontal" short "boxd_lddshhz"]
dict set charinfo 9549 [list desc "Box Drawings Heavy Double Dash Horizontal" short "boxd_hddshhz"]
dict set charinfo 9550 [list desc "Box Drawings Light Double Dash Vertical" short "boxd_lddshv"]
dict set charinfo 9551 [list desc "Box Drawings Heavy Double Dash Vertical" short "boxd_hddshv"]
dict set charinfo 9552 [list desc "Box Drawings Double Horizontal" short "boxd_dhz"]
dict set charinfo 9553 [list desc "Box Drawings Double Vertical" short "boxd_dv"]
dict set charinfo 9554 [list desc "Box Drawings Down Single and Right Double" short "boxd_dsrd"]
dict set charinfo 9555 [list desc "Box Drawings Down Double and Right Single" short "boxd_ddrs"]
dict set charinfo 9556 [list desc "Box Drawings Double Down and Right" short "boxd_ddr"]
dict set charinfo 9557 [list desc "Box Drawings Down Single and Left Double" short "boxd_dsld"]
dict set charinfo 9558 [list desc "Box Drawings Down Double and Left Single" short "boxd_ddls"]
dict set charinfo 9559 [list desc "Box Drawings Double Down and Left" short "boxd_ddl"]
dict set charinfo 9560 [list desc "Box Drawings Up Single and Right Double" short "boxd_usrd"]
dict set charinfo 9561 [list desc "Box Drawings Up Double and Right Single" short "boxd_udrs"]
dict set charinfo 9562 [list desc "Box Drawings Double Up and Right" short "boxd_dur"]
dict set charinfo 9563 [list desc "Box Drawings Up Single and Left Double" short "boxd_usld"]
dict set charinfo 9564 [list desc "Box Drawings Up Double and Left Single" short "boxd_udls"]
dict set charinfo 9565 [list desc "Box Drawings Double Up and Left" short "boxd_dul"]
dict set charinfo 9566 [list desc "Box Drawings Vertical Single and Right Double" short "boxd_vsrd"]
dict set charinfo 9567 [list desc "Box Drawings Vertical Double and Right Single" short "boxd_vdrs"]
dict set charinfo 9568 [list desc "Box Drawings Double Vertical and Right" short "boxd_dvr"]
dict set charinfo 9569 [list desc "Box Drawings Vertical Single and Left Double" short "boxd_vsld"]
dict set charinfo 9570 [list desc "Box Drawings Vertical Double and Left Single" short "boxd_vdls"]
dict set charinfo 9571 [list desc "Box Drawings Double Vertical and Left" short "boxd_dvl"]
dict set charinfo 9572 [list desc "Box Drawings Down Single and Horizontal Double" short "boxd_dshzd"]
dict set charinfo 9573 [list desc "Box Drawings Down Double and Horizontal Single" short "boxd_ddhzs"]
dict set charinfo 9574 [list desc "Box Drawings Double Down and Horizontal" short "boxd_ddhz"]
dict set charinfo 9575 [list desc "Box Drawings Up Single and Horizontal Double" short "boxd_ushzd"]
dict set charinfo 9576 [list desc "Box Drawings Up Double and Horizontal Single" short "boxd_udhzs"]
dict set charinfo 9577 [list desc "Box Drawings Double Up and Horizontal" short "boxd_duhz"]
dict set charinfo 9578 [list desc "Box Drawings Vertical Single and Horizontal Double" short "boxd_vshzd"]
dict set charinfo 9579 [list desc "Box Drawings Vertical Double and Horizontal Single" short "boxd_vdhzs"]
dict set charinfo 9580 [list desc "Box Drawings Double Vertical and Horizontal" short "boxd_dvhz"]
dict set charinfo 9581 [list desc "Box Drawings Light Arc Down and Right" short "boxd_ladr"]
dict set charinfo 9582 [list desc "Box Drawings Light Arc Down and Left" short "boxd_ladl"]
dict set charinfo 9583 [list desc "Box Drawings Light Arc Up and Left" short "boxd_laul"]
dict set charinfo 9584 [list desc "Box Drawings Light Arc Up and Right" short "boxd_laur"]
dict set charinfo 9585 [list desc "Box Drawings Light Diagonal Upper Right To Lower Left" short "boxd_ldgurll"]
dict set charinfo 9586 [list desc "Box Drawings Light Diagonal Upper Left To Lower Right" short "boxd_ldgullr"]
dict set charinfo 9587 [list desc "Box Drawings Light Diagonal Cross" short "boxd_ldc"]
dict set charinfo 9588 [list desc "Box Drawings Light Left" short "boxd_ll"]
dict set charinfo 9589 [list desc "Box Drawings Light Up" short "boxd_lu"]
dict set charinfo 9590 [list desc "Box Drawings Light Right" short "boxd_lr"]
dict set charinfo 9591 [list desc "Box Drawings Light Down" short "boxd_ld"]
dict set charinfo 9592 [list desc "Box Drawings Heavy Left" short "boxd_hl"]
dict set charinfo 9593 [list desc "Box Drawings Heavy Up" short "boxd_hu"]
dict set charinfo 9594 [list desc "Box Drawings Heavy Right" short "boxd_hr"]
dict set charinfo 9595 [list desc "Box Drawings Heavy Down" short "boxd_hd"]
dict set charinfo 9596 [list desc "Box Drawings Light Left and Heavy Right" short "boxd_llhr"]
dict set charinfo 9597 [list desc "Box Drawings Light Up and Heavy Down" short "boxd_luhd"]
dict set charinfo 9598 [list desc "Box Drawings Heavy Left and Light Right" short "boxd_hllr"]
dict set charinfo 9599 [list desc "Box Drawings Heavy Up and Light Down" short "boxd_huld"]
dict set charsets "halfwidth_and_fullwidth_forms" [list ranges [list {start 65280 end 65519}] description "Halfwidth and Fullwidth Forms (variants)"]
dict set charsets "ascii_fullwidth" [list ranges [list {start 65281 end 65374}] description "Ascii 21 to 7E fullwidth" parentblock "halfwidth_and_fullwidth_forms"]
dict set charsets "specials" [list ranges [list {start 65520 end 65535}] description "Specials"]
#build dicts keyed on short
variable charshort
proc _build_charshort {} {
variable charshort
set charshort [dict create]
variable charinfo
dict for {k v} $charinfo {
set sh [dict get $v short]
if {[dict exists $charshort $sh]} {
puts stderr "_build_charshort WARNING character data load duplicate shortcode '$sh'"
}
dict set charshort $sh [format %c $k]
}
return [dict size $charshort]
}
_build_charshort
proc load_nerdfonts {} {
variable charsets
variable charinfo
package require fileutil
set ver [package provide punk::char]
if {$ver ne ""} {
set ifneeded [package ifneeded punk::char [package provide punk::char]]
#puts stderr "punk::char ifneeded script: $ifneeded"
lassign [split $ifneeded ";"] _ sourceinfo
set basedir [file dirname [lindex $sourceinfo end]]
} else {
#review - will only work at package load time
set scr [info script]
if {$scr eq ""} {
error "load_nerdfonts unable to determine package folder"
}
set basedir [file dirname [info script]]
}
set pkg_data_dir [file join $basedir char]
set fname [file join $pkg_data_dir nerd-fonts-glyph-list.txt]
if {[file exists $fname]} {
#puts stderr "load_nerdfonts loading $fname"
set data [fileutil::cat -translation binary $fname]
set short_seen [list]
set current_set_range [dict create]
foreach ln [split $data \n] {
set ln [string trim $ln]
if {$ln eq ""} {continue}
set desc [lassign $ln hex set]
set hexnum 0x$hex
set dec [expr $hexnum]
set setname "nf_$set" ;#Ensure nerdfont set names are prefixed.
if {$setname ni [dict keys $charsets]} {
set newrange [list start $dec end $dec]
dict set current_set_range $setname $newrange
dict set charsets $setname [list ranges [list $newrange] description "nerd fonts $set"]
} else {
set existing_range [dict get $current_set_range $setname]
set existing_end [dict get $existing_range end]
if {$dec - $existing_end == 1} {
#part of current range
dict set current_set_range $setname end $dec
#overwrite last ranges element
set rangelist [lrange [dict get $charsets $setname ranges] 0 end-1]
lappend rangelist [dict get $current_set_range $setname]
dict set charsets $setname ranges $rangelist
} else {
#new range for set
dict set current_set_range $setname start $dec
dict set current_set_range $setname end $dec
set rangelist [dict get $charsets $setname ranges]
lappend rangelist [dict get $current_set_range $setname]
dict set charsets $setname ranges $rangelist
}
#dict set charsets $setname end $dec
}
if {$hex ni [dict keys $charinfo]} {
# -- ---
#review
set map [list beaufort bf gibbous gb crescent cr thunderstorm tstorm thermometer thermom]
lappend map {*}[list directory dir creativecommons ccom creative_commons ccom forwardslash fs]
lappend map {*}[list multimedia mm multiple multi outline outl language lang]
lappend map {*}[list odnoklassniki okru]
# -- ---
#consider other ways to unambiguously shorten names?
#normalize nf_fa & nf_fa 'o' element to 'outl' so outlines can be searched across sets more easily (o not necessarily at last position)
set normdesc [list]
foreach el $desc {
if {$el eq "o"} {
set el "outl"
}
lappend normdesc $el
}
set joined_desc [join $normdesc _]
#map after join so we can normalize some underscored elements e.g creativecommons & creative_commons
set mapped_desc [string map $map $joined_desc]
set s nf_${set}_$mapped_desc
if {$s ni $short_seen} {
lappend short_seen $s
} else {
#duplicate in the data file (e.g 2023 weather night alt rain mix)
set s ${s}_$hex
}
dict set charinfo $dec [list desc "$desc" short $s]
}
}
_build_charshort
} else {
puts stderr "unable to find glyph file. Tried $fname"
}
}
proc charshort {shortname} {
variable charshort
return [dict get $charshort $shortname]
}
proc box_drawing {args} {
return [charset "box_drawing" {*}$args]
}
proc box_drawing_dict {} {
return [charset_dict "box_drawing"]
}
proc char_range_dict {start end args} {
if {![string is integer -strict $start] || ![string is integer -strict $end]} {
error "char_range_dict error start and end must be integers"
}
set and_globs [list]
if {![llength $args]} {
set args [list *]
}
foreach glob $args {
if {![regexp {[*?]} $glob]} {
lappend and_globs "*$glob*"
} else {
lappend and_globs $glob
}
}
variable charinfo
set cdict [dict create]
for {set i $start} {$i <= $end} {incr i} {
set hx [format %04x $i]
set ch [format %c $i]
if {[dict exists $charinfo $i]} {
set d [dict get $charinfo $i desc]
set s [dict get $charinfo $i short]
} else {
set d ""
set s ""
}
set matchcount 0
foreach glob $and_globs {
if {[string match -nocase $glob $s] || [string match -nocase $glob $d]} {
incr matchcount
}
}
if {$matchcount == [llength $and_globs]} {
dict set cdict $hx [list char $ch desc $d short $s dec $i]
}
}
return $cdict
}
proc char_range {start end args} {
if {![string is integer -strict $start] || ![string is integer -strict $end]} {
error "char_range error start and end must be integers"
}
set charset_dict [char_range_dict $start $end {*}$args]
set out ""
set col3 [string repeat " " 12]
dict for {k inf} $charset_dict {
set s [dict get $inf short]
set d [dict get $inf desc]
set s_col [overtype::left $col3 $s]
append out "$k [dict get $inf char] $s_col $d" \n
}
return $out
}
proc charset_names {} {
variable charsets
return [dict keys $charsets]
}
proc charset_dict {name} {
variable charsets
if {$name ni [charset_names]} {
error "unknown charset '$name' - use 'charset_names' to get list"
}
set setinfo [dict get $charsets $name]
set ranges [dict get $setinfo ranges]
set charset_dict [dict create]
foreach r $ranges {
set start [dict get $r start]
set end [dict get $r end]
set charset_dict [dict merge $charset_dict [char_range_dict $start $end]]
}
return $charset_dict
}
proc charset {namesearch args} {
variable charsets
if {[regexp {[?*]} $namesearch]} {
#name glob search
} else {
if {$namesearch ni [charset_names]} {
error "unknown charset '$namesearch' - use 'charset_names' to get list"
}
}
set out ""
set matched_names [dict keys $charsets $namesearch]
foreach name $matched_names {
set setinfo [dict get $charsets $name]
set ranges [dict get $setinfo ranges]
set charset_dict [dict create]
foreach r $ranges {
set start [dict get $r start]
set end [dict get $r end]
set charset_dict [dict merge $charset_dict [char_range_dict $start $end {*}$args]]
}
set col_items_short [list]
set col_items_desc [list]
dict for {k inf} $charset_dict {
lappend col_items_short [dict get $inf short]
lappend col_items_desc [dict get $inf desc]
}
if {[llength $col_items_short]} {
set widest3 [tcl::mathfunc::max {*}[lmap v $col_items_short {string length $v}]]
if {$widest3 == 0} {
set col3 " "
} else {
set col3 [string repeat " " $widest3]
}
dict for {k inf} $charset_dict {
set s [dict get $inf short]
set d [dict get $inf desc]
set s_col [overtype::left $col3 $s]
append out "$k [dict get $inf char] $s_col $d" \n
}
}
}
return $out
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::char [namespace eval punk::char {
variable version
set version 999999.0a1.0
}]
return

3
src/modules/punk/char-buildversion.txt

@ -0,0 +1,3 @@
0.1.0
#First line must be a semantic version number
#all other lines are ignored.

3692
src/modules/punk/char/glyph-list.txt

File diff suppressed because it is too large Load Diff

168
src/modules/punk/console-999999.0a1.0.tm

@ -0,0 +1,168 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application punk::console 999999.0a1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
if {"windows" eq $::tcl_platform(platform)} {
package require zzzload
zzzload::pkg_require twapi
}
#see https://learn.microsoft.com/en-us/windows/console/classic-vs-vt
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::console {
variable has_twapi 0
if {"windows" eq $::tcl_platform(platform)} {
proc enableRaw {{channel stdin}} {
define_windows_procs
tailcall enableRaw $channel
}
proc disableRaw {{channel stdin}} {
define_windows_procs
tailcall disableRaw $channel
}
} else {
proc enableRaw {{channel stdin}} {
set sttycmd [auto_execok stty]
exec {*}$sttycmd raw -echo <@$channel
}
proc disableRaw {{channel stdin}} {
set sttycmd [auto_execok stty]
exec {*}$sttycmd raw echo <@$channel
}
}
proc define_windows_procs {} {
set loadstate [zzzload::pkg_require twapi]
if {$loadstate ni [list loading failed]} {
package require twapi ;#should be fast once twapi dll loaded in zzzload thread
set ::punk::console::has_twapi 1
proc enableRaw {{channel stdin}} {
#review - change to modify_console_input_mode
set console_handle [twapi::GetStdHandle -10]
set oldmode [twapi::GetConsoleMode $console_handle]
set newmode [expr {$oldmode & ~6}] ;# Turn off the echo and line-editing bits
twapi::SetConsoleMode $console_handle $newmode
}
proc disableRaw {{channel stdin}} {
set console_handle [twapi::GetStdHandle -10]
set oldmode [twapi::GetConsoleMode $console_handle]
set newmode [expr {$oldmode | 6}] ;# Turn on the echo and line-editing bits
twapi::SetConsoleMode $console_handle $newmode
}
} else {
if {$loadstate eq "failed"} {
puts stderr "punk::console falling back to stty because twapi load failed"
proc enableRaw {{channel stdin}} {
set sttycmd [auto_execok stty]
exec {*}$sttycmd raw -echo <@$channel
}
proc disableRaw {{channel stdin}} {
set sttycmd [auto_execok stty]
exec {*}$sttycmd raw echo <@$channel
}
}
tailcall du_dirlisting_generic $folderpath {*}$args
}
}
proc ansi_response_handler {chan} {
set status [catch {read $chan 1} bytes]
if { $status != 0 } {
# Error on the channel
puts "error reading $chan: $bytes"
set ::punk::console::chunkdone 2
} elseif {$bytes ne ""} {
# Successfully read the channel
#puts "got: [string length $bytes]"
append ::punk::console::chunk $bytes
if {$bytes eq "R"} {
set ::punk::console::chunkdone 4
} else {
fileevent stdin readable [list ::punk::console::ansi_response_handler stdin]
}
} elseif { [eof $chan] } {
# End of file on the channel
#review
puts "ansi_response_handler end of file"
set ::punk::console::chunkdone 1
} elseif { [fblocked $chan] } {
# Read blocked. Just return
} else {
# Something else
puts "ansi_response_handler can't happen"
set ::punk::console::chunkdone 3
}
}
proc get_cursor_position {} {
set ::punk::console::chunk ""
enableRaw
puts -nonewline stdout \033\[6n ;flush stdout
#e.g \033\[46;1R
#todo - reset
fconfigure stdin -blocking 0
fileevent stdin readable [list ::punk::console::ansi_response_handler stdin]
set info ""
vwait ::punk::console::chunkdone
fileevent stdin readable {}
disableRaw
set info $::punk::console::chunk
#set punk::console::chunk ""
set data [string range $info 2 end-1]
return [split $data ";"]
}
proc test {} {
enableRaw
puts -nonewline stdout \033\[6n ;flush stdout
fconfigure stdin -blocking 0
set info [read stdin 20] ;#
after 1
if {[string first "R" $info] <=0} {
append info [read stdin 20]
}
disableRaw
set data [string range [string trim $info] 2 end-1]
return [split $data ";"]
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::console [namespace eval punk::console {
variable version
set version 999999.0a1.0
}]
return

3
src/modules/punk/console-buildversion.txt

@ -0,0 +1,3 @@
0.1.0
#First line must be a semantic version number
#all other lines are ignored.

11
src/modules/punk/mix-0.2.tm

@ -1,10 +1,15 @@
package require punk::cap
package require punk::mix::templates ;#registers 'templates' capability with punk::cap
package require punk::mix::base
package require punk::mix::cli
namespace eval punk::mix {
}
package provide punk::mix [namespace eval punk::mix {
variable version
set version 0.2
variable version
set version 0.2
}]

79
src/modules/punk/mix/base-0.1.tm

@ -314,15 +314,6 @@ namespace eval punk::mix::base {
#-----------------------------------------------------
proc mix_templates_dir {} {
set provide_statement [package ifneeded punk::mix [package require punk::mix]]
set tmdir [file dirname [lindex $provide_statement end]]
set tpldir $tmdir/mix/templates
if {![file exists $tpldir]} {
error "punk::mix::lib::mix_templates_dir unable to locate mix templates folder at '$tpldir'"
}
return $tpldir
}
#find src/something folders which are not certain known folders with other purposes, (such as: bootsupport .vfs folders or vendor folders etc) and contain .tm file(s)
proc find_source_module_paths {{path {}}} {
@ -352,12 +343,48 @@ namespace eval punk::mix::base {
}
return $tm_folders
}
proc mix_templates_dir {} {
puts stderr "mix_templates_dir WARNING: deprecated - use get_template_folders instead"
set provide_statement [package ifneeded punk::mix [package require punk::mix]]
set tmdir [file dirname [lindex $provide_statement end]]
set tpldir $tmdir/mix/templates
if {![file exists $tpldir]} {
error "punk::mix::lib::mix_templates_dir unable to locate mix templates folder at '$tpldir'"
}
return $tpldir
}
#get_template_folders
# scriptpath - file or folder
# It represents the base point from which to search for mixtemplates folders either directly related to the scriptpath (../) or in the containing project if any
# The cwd will also be searched for project root - but with lower precedence in the resultset (later in list)
proc get_template_folders {{scriptpath ""}} {
set folders [list]
#1 lowest precedence - templates from packages (ordered by order in which packages registered with punk::cap)
set folderdict [dict create]
set template_folder_dict [punk::cap::templates::folders]
dict for {dir folderinfo} $template_folder_dict {
dict set folderdict $dir $folderinfo
}
#2 middle precedence - mixtemplates folder relative to cwd
set searchbase [pwd]
set fld [file join $searchbase mixtemplates]
if {[file isdirectory $fld]} {
if {![dict exists $folderdict $fld]} {
dict set folderdict $fld [list source $searchbase sourcetype cwd]
}
}
set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} {
set fld [file join $pwd_projectroot src/mixtemplates]
if {![dict exists $folderdict $fld]} {
dict set folderdict $fld [list source $pwd_projectroot sourcetype project]
}
}
#3 highest precedence - mixtemplates relative to scriptpath argument
if {$scriptpath ne ""} {
if {[file type $scriptpath] eq "file"} {
set searchbase [file dirname $scriptpath]
@ -365,43 +392,19 @@ namespace eval punk::mix::base {
set searchbase $scriptpath
}
if {[file isdirectory [file join $searchbase mixtemplates]]} {
lappend folders [file join $searchbase mixtemplates]
dict set folderdict [file join $searchbase mixtemplates] [list source $searchbase sourcetype pathsearch]
}
set pathinfo [punk::repo::find_repos $searchbase]
set scriptpath_projectroot [dict get $pathinfo closest]
if {$scriptpath_projectroot ne ""} {
set fld [file join $scriptpath_projectroot src/mixtemplates]
if {[file isdirectory $fld]} {
if {$fld ni $folders} {
lappend folders $fld
}
dict set folderdict $fld [list source $scriptpath_projectroot sourcetype project]
}
}
}
set searchbase [pwd]
set fld [file join $searchbase mixtemplates]
if {[file isdirectory $fld]} {
if {$fld ni $folders} {
lappend folders $fld
}
}
set pathinfo [punk::repo::find_repos $searchbase]
set pwd_projectroot [dict get $pathinfo closest]
if {$pwd_projectroot ne ""} {
set fld [file join $pwd_projectroot src/mixtemplates]
if {[file isdirectory $fld]} {
if {$fld ni $folders} {
lappend folders $fld
}
}
}
set fld [::punk::mix::base::lib::mix_templates_dir]
if {[file isdirectory $fld]} {
if {$fld ni $folders} {
lappend folders $fld
}
}
return $folders
#don't sort - order in which encountered defines the precedence - with later overriding earlier
return $folderdict
}
proc module_subpath {modulename} {

35
src/modules/punk/mix/commandset/debug-999999.0a1.0.tm

@ -23,19 +23,46 @@
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::mix::commandset::debug {
namespace export paths
namespace export get paths
namespace path ::punk::mix::cli
#Except for 'get' - all debug commands should emit to stdout
proc paths {} {
set out ""
puts stdout "find_repos output:"
set pathinfo [punk::repo::find_repos [pwd]]
puts stdout "pathinfo: $pathinfo"
pdict $pathinfo
set projectdir [dict get $pathinfo closest]
puts stdout "closest projectdir: $projectdir"
set modulefolders [lib::find_source_module_paths $projectdir]
puts stdout "modulefolders: $modulefolders"
return
set template_folder_dict [punk::mix::base::lib::get_template_folders]
puts stdout "get_template_folders output:"
pdict $template_folder_dict
return
}
#call other debug command - but capture stdout as return value
proc get {args} {
set nm [lindex $args 0]
if {$nm eq ""} {
set nscmds [info commands [namespace current]::*]
set cmds [lmap v $nscmds {namespace tail $v}]
error "debug.get missing debug command argument. Try one of: $cmds"
return
}
set nextargs [lrange $args 1 end]
set out ""
if {[info commands [namespace current]::$nm] ne ""} {
append out [runout -n -tcl [namespace current]::$nm {*}$nextargs] \n
} else {
set nscmds [info commands [namespace current]::*]
set cmds [lmap v $nscmds {namespace tail $v}]
error "debug.get invalid debug command '$nm' Try one of: $cmds"
}
return $out
}
namespace eval lib {
}

83
src/modules/punk/mix/commandset/layout-999999.0a1.0.tm

@ -38,13 +38,24 @@ namespace eval punk::mix::commandset::layout {
return [join $templatefiles \n]
}
proc templatefiles.relative {layout} {
set tpldir [::punk::mix::base::lib::mix_templates_dir]
set layout_base $tpldir/layouts
set layout_dir [file join $layout_base $layout]
if {![file exists $layout_dir]} {
puts stderr "Unable to locate folder for layout '$layout' at $layout_dir"
set template_folder_dict [punk::mix::base::lib::get_template_folders]
set tpldirs [list]
dict for {dir folderinfo} $template_folder_dict {
if {[file exists $dir/layouts/$layout]} {
lappend tpldirs $dir
}
}
if {![llength $tpldirs]} {
puts stderr "Unable to locate folder for layout '$layout'"
puts stderr "searched [dict size $template_folder_dict] template folders"
return
}
set tpldir [lindex $tpldirs end]
set layout_base $tpldir/layouts
set layout_dir [file join $layout_base $layout]
set stripprefix [file normalize $layout_dir]
set templatefiles [lib::layout_scan_for_template_files $layout]
set tails [list]
@ -61,22 +72,40 @@ namespace eval punk::mix::commandset::layout {
if {![string length $glob]} {
set glob *
}
set tpldir [::punk::mix::base::lib::mix_templates_dir]
set layout_base $tpldir/layouts
set layouts [glob -nocomplain -dir $layout_base -type d -tail *]
set layouts [lsort $layouts]
if {$glob ne "*"} {
set layouts [lsearch -all -inline $layouts $glob]
set layouts [list]
#set tplfolderdict [punk::cap::templates::folders]
set tplfolderdict [punk::mix::base::lib::get_template_folders]
dict for {tpldir folderinfo} $tplfolderdict {
set layout_base $tpldir/layouts
#collect all layouts and use lsearch glob rather than the filesystem glob (avoid issues with dotted folder names)
set all_layouts [lsort [glob -nocomplain -dir $layout_base -type d -tail *]]
foreach match [lsearch -all -inline $all_layouts $glob] {
lappend layouts [list $match $folderinfo]
}
}
return [join [lsort $layouts] \n]
return [join [lsort -index 0 $layouts] \n]
}
}
namespace eval lib {
proc layout_all_files {layout} {
set tpldir [::punk::mix::base::lib::mix_templates_dir]
set layoutfolder $tpldir/layouts/$layout
set tplfolderdict [punk::mix::base::lib::get_template_folders]
set layouts_found [list]
dict for {tpldir folderinfo} $tplfolderdict {
if {[file isdirectory $tpldir/layouts/$layout]} {
lappend layouts_found $tpldir/layouts/$layout
}
}
if {![llength $layouts_found]} {
puts stderr "layout '$layout' not found."
puts stderr "searched [dict size $tplfolderdict] template folders"
dict for {tpldir pkg} $tplfolderdict {
puts stderr " - $tpldir $pkg"
}
return
}
set layoutfolder [lindex $layouts_found end]
if {![file isdirectory $layoutfolder]} {
puts stderr "layout '$layout' not found in $tpldir/layouts"
}
@ -87,13 +116,29 @@ namespace eval punk::mix::commandset::layout {
return $file_list
}
#
#todo - allow specifying which package the layout is from: e.g "punk::mix::templates project" ??
proc layout_scan_for_template_files {layout {tags {}}} {
#equivalent for projects? punk::mix::commandset::module::lib::templates_dict -scriptpath ""
set tpldir [::punk::mix::base::lib::mix_templates_dir]
set layoutfolder $tpldir/layouts/$layout
if {![file isdirectory $layoutfolder]} {
puts stderr "layout '$layout' not found in $tpldir/layouts"
set tplfolderdict [punk::cap::templates::folders]
set layouts_found [list]
dict for {tpldir pkg} $tplfolderdict {
if {[file isdirectory $tpldir/layouts/$layout]} {
lappend layouts_found $tpldir/layouts/$layout
}
}
if {![llength $layouts_found]} {
puts stderr "layout '$layout' not found."
puts stderr "searched [dict size $tplfolderdict] template folders"
dict for {tpldir pkg} $tplfolderdict {
puts stderr " - $tpldir $pkg"
}
return
}
set layoutfolder [lindex $layouts_found end]
#use last matching layout found. review silent if multiple?
if {![llength $tags]} {
#todo - get standard tags from somewhere
set tags [list %project%]

3
src/modules/punk/mix/commandset/module-999999.0a1.0.tm

@ -170,7 +170,8 @@ namespace eval punk::mix::commandset::module {
set testdir [pwd]
if {![string length [set projectdir [punk::repo::find_project $testdir]]]} {
if {![string length [set projectdir [punk::repo::find_candidate $testdir]]]} {
error "module.new unable to create module in projectdir:$projectdir - directory doesn't appear to meet basic standards (/src, src/modules, src/lib & /modules must exist, must not be a system path such as /usr/bin or c:/windows)"
set msg [punkc::repo::is_candidate_root_requirements_msg]
error "module.new unable to create module in projectdir:$projectdir - directory doesn't appear to meet basic standards $msg"
}
}
if {$opt_project == "\uFFFF"} {

45
src/modules/punk/mix/commandset/project-999999.0a1.0.tm

@ -53,6 +53,12 @@ namespace eval punk::mix::commandset::project {
-modules \uFFFF\
-layout project
] ;#todo
set known_opts [dict keys $defaults]
foreach {k v} $args {
if {$k ni $known_opts} {
error "project.new error: option '$k' not known. Known options: $known_opts"
}
}
set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_type [dict get $opts -type]
@ -150,11 +156,26 @@ namespace eval punk::mix::commandset::project {
#punk::mix::base::lib::get_template_folders
#punk::mix::commandset::module::lib::templates_dict -scriptpath ""
set tpldir [punk::mix::cli::lib::mix_templates_dir]
set template_folder_dict [punk::mix::base::lib::get_template_folders]
set tpldirs [list]
dict for {dir folderinfo} $template_folder_dict {
if {[file exists $dir/layouts/$opt_layout]} {
lappend tpldirs $dir
}
}
if {![llength $tpldirs]} {
puts stderr "layout '$opt_layout' was not found in template dirs"
puts stderr "searched [dict size $template_folder_dict] template folders"
dict for {dir folderinfo} $template_folder_dict {
puts stderr " - $dir $folderinfo"
}
return
}
#review: silently use last entry which had the layout (?)
set tpldir [lindex $tpldirs end]
if {[file exists $projectdir] && !($opt_force || $opt_update)} {
puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template"
@ -250,14 +271,20 @@ namespace eval punk::mix::commandset::project {
cd $projectdir
foreach m $opt_modules {
punk::mix::commandset::module::new $m -project $projectname -type $opt_type -force $opt_force
if {[file exists $projectdir/src/modules]} {
foreach m $opt_modules {
punk::mix::commandset::module::new $m -project $projectname -type $opt_type -force $opt_force
}
} else {
puts stderr "project.new WARNING template hasn't created src/modules - skipping creation of new module(s) for project"
}
#generate www/man/md output in 'embedded' folder which should be checked into repo for online documentation
cd $projectdir/src
punk::mix::cli::lib::kettle_call lib doc
#Kettle doc
if {[file exists $projectdir/src]} {
cd $projectdir/src
punk::mix::cli::lib::kettle_call lib doc
#Kettle doc
}
cd $projectdir

22
src/modules/punk/mix/commandset/repo-999999.0a1.0.tm

@ -46,6 +46,28 @@ namespace eval punk::mix::commandset::repo {
#remove/archive .fossil
puts stderr "unimplemented"
}
proc state {} {
set result ""
set repopaths [punk::repo::find_repos [pwd]]
set repos [dict get $repopaths repos]
if {![llength $repos]} {
append result [dict get $repopaths warnings]
} else {
append result [dict get $repopaths warnings]
lassign [lindex $repos 0] repopath repotypes
if {"fossil" in $repotypes} {
append result \n "Fossil repo based at $repopath"
set repostate [punk::repo::workingdir_state $repopath -repopaths $repopaths -repotypes fossil]
append result \n [punk::repo::workingdir_state_summary $repostate]
}
if {"git" in $repotypes} {
append result \n "Git repo based at $repopath"
set repostate [punk::repo::workingdir_state $repopath -repopaths $repopaths -repotypes git]
append result \n [punk::repo::workingdir_state_summary $repostate]
}
}
return $result
}
}

38
src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm

@ -239,15 +239,29 @@ namespace eval punk::mix::commandset::scriptwrap {
}
#process_extensions - either a single one - or all found or as per .wrapconfig
set libwrapper_folder_default [file join [::punk::mix::base::lib::mix_templates_dir] utility scriptappwrappers]
if {$opt_template eq "\uFFFF"} {
set templatename punk-multishell.cmd
} else {
set templatename $opt_template
}
set template_folder_dict [punk::mix::template_folders]
set tpldirs [list]
dict for {dir pkg} $template_folder_dict {
if {[file exists $dir/utility/scriptappwrappers/$templatename]} {
lappend tpldirs $dir
}
}
if {[string length $customwrapper_folder] && [file exists [file join $customwrapper_folder $templatename] ]} {
set wrapper_template [file join $customwrapper_folder $templatename]
} else {
if {![llength $tpldirs]} {
set msg "No template named '$templatename' found in src/scriptapps/wrappers or in template dirs from packages"
append msg \n "Searched [dict size $template_folder_dict] template dirs"
error $msg
}
set libwrapper_folder_default [file join [lindex $tpldir end] utility scriptappwrappers]
set wrapper_template [file join $libwrapper_folder_default $templatename]
}
@ -419,10 +433,20 @@ namespace eval punk::mix::commandset::scriptwrap {
}
}
}
set fld [file join [::punk::mix::base::lib::mix_templates_dir] utility scriptappwrappers]
if {[file isdirectory $fld]} {
if {$fld ni $wrapper_folders} {
lappend wrapper_folders $fld
set template_folder_dict [punk::mix::template_folders]
set tpldirs [list]
dict for {dir pkg} $template_folder_dict {
if {[file exists $dir/utility/scriptappwrappers]} {
lappend tpldirs $dir
}
}
foreach tpldir $tpldirs {
set fld [file join $tpldir utility scriptappwrappers]
if {[file isdirectory $fld]} {
if {$fld ni $wrapper_folders} {
lappend wrapper_folders $fld
}
}
}
return $wrapper_folders

49
src/modules/punk/mix/templates-999999.0a1.0.tm

@ -0,0 +1,49 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2023
#
# @@ Meta Begin
# Application punk::mix::templates 999999.0a1.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
package require punk::cap
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::mix::templates {
punk::cap::register_package punk::mix::templates [list\
{templates {relpath ../templates}}\
]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::mix::templates [namespace eval punk::mix::templates {
variable version
set version 999999.0a1.0
}]
return

3
src/modules/punk/mix/templates-buildversion.txt

@ -0,0 +1,3 @@
0.1.0
#First line must be a semantic version number
#all other lines are ignored.

251
src/modules/punk/mix/templates/layouts/project/src/make.tcl

@ -269,7 +269,12 @@ file mkdir $target_modules_base
#external libs and modules first - and any supporting files - no 'building' required
if {[file exists $sourcefolder/vendorlib]} {
set resultdict [punkcheck::install $sourcefolder/vendorlib $projectroot/lib -overwrite ALL-TARGETS]
#unpublish README.md from source folder - but on the root one
set unpublish [list\
README.md\
]
set resultdict [punkcheck::install $sourcefolder/vendorlib $projectroot/lib -overwrite installedsourcechanged-targets -unpublish_paths $unpublish]
set copied [dict get $resultdict files_copied]
set sources_unchanged [dict get $resultdict sources_unchanged]
puts stdout "--------------------------"
@ -286,7 +291,7 @@ if {[file exists $sourcefolder/vendorlib]} {
if {[file exists $sourcefolder/vendormodules]} {
#install .tm *and other files*
set resultdict [punkcheck::install $sourcefolder/vendormodules $target_modules_base -installer make.tcl -overwrite installedsourcechanged-targets]
set resultdict [punkcheck::install $sourcefolder/vendormodules $target_modules_base -installer make.tcl -overwrite installedsourcechanged-targets -unpublish_paths {README.md}]
set copied [dict get $resultdict files_copied]
set sources_unchanged [dict get $resultdict sources_unchanged]
puts stdout "--------------------------"
@ -315,7 +320,7 @@ foreach src_module_dir $source_module_folderlist {
set overwrite "installedsourcechanged-targets"
#set overwrite "ALL-TARGETS"
set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite]
set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -unpublish_paths {README.md}]
set copied [dict get $resultdict files_copied]
set sources_unchanged [dict get $resultdict sources_unchanged]
puts stdout "--------------------------"
@ -373,9 +378,14 @@ set runtimefile [lindex $runtimes 0]
#}
set basedir $buildfolder
set target_relpath [punkcheck::lib::path_relative $basedir $buildfolder/buildruntime.exe]
set config [dict create\
-make-step copy_runtime\
]
lassign [punkcheck::start_installer_event $basedir/.punkcheck $installername $rtfolder $buildfolder $config] _eventid punkcheck_eventid _recordset record_list
set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername]
set target_relpath [punkcheck::lib::path_relative $basedir $buildfolder/buildruntime.exe]
set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid]
# -- --- --- --- --- ---
set source_relpath [punkcheck::lib::path_relative $basedir $rtfolder/$runtimefile]
set file_record [punkcheck::installfile_add_source_and_fetch_metadata $basedir $source_relpath $file_record]
@ -398,133 +408,172 @@ if {[llength [dict get $changed_unchanged changed]]} {
set startdir [pwd]
puts stdout "Found [llength $vfs_folders] .vfs folders - building executable for each..."
cd [file dirname $buildfolder]
#root folder mtime is insufficient for change detection. Tree mtime of folders only is a barely passable mechanism for vfs change detection in some circumstances - e.g if files added/removed but never edited in place
#a hash of full tree file & dir mtime may be more reasonable - but it remains to be seen if just tar & checksum is any/much slower.
#Simply rebuilding all the time may be close the speed of detecting change anyway - and almost certainly much faster when there is a change.
#Using first mtime encountered that is later than target is another option - but likely to be highly variable in speed. Last file in the tree could happen to be the latest, and this mechanism doesn't handle build on reversion to older source.
foreach vfs $vfs_folders {
set vfsname [file rootname $vfs]
puts stdout " Processing vfs $sourcefolder/$vfs"
puts stdout " ------------------------------------"
set skipped_vfs_build 0
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set basedir $buildfolder
set config [dict create\
-make-step build_vfs\
]
lassign [punkcheck::start_installer_event $basedir/.punkcheck $installername $sourcefolder $buildfolder $config] _eventid punkcheck_eventid _recordset record_list
set target_relpath [punkcheck::lib::path_relative $basedir $buildfolder/$vfsname.exe]
set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid]
# -- --- --- --- --- ---
set source_relpath [punkcheck::lib::path_relative $basedir $sourcefolder/$vfs]
set file_record [punkcheck::installfile_add_source_and_fetch_metadata $basedir $source_relpath $file_record]
# -- --- --- --- --- ---
set changed_unchanged [punkcheck::recordlist::file_install_record_source_changes [lindex [dict get $file_record body] end]]
if {[llength [dict get $changed_unchanged changed]]} {
set file_record [punkcheck::installfile_started_install $basedir $file_record]
# -- --- --- --- --- ---
if {[file exists $buildfolder/$vfsname]} {
puts stderr "deleting existing $buildfolder/$vfsname"
file delete $sourcefolder/_build/$vfsname
}
puts stdout "building $vfsname with sdx.. vfsdir:$vfs cwd: [pwd]"
if {[file exists $buildfolder/$vfsname]} {
puts stderr "deleting existing $buildfolder/$vfsname"
file delete $sourcefolder/_build/$vfsname
}
puts stdout "building $vfsname with sdx.. vfsdir:$vfs cwd: [pwd]"
if {[catch {
exec sdx wrap $buildfolder/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose
} result]} {
puts stderr "sdx wrap _build/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose failed with msg: $result"
} else {
puts stdout "ok - finished sdx"
set separator [string repeat = 40]
puts stdout $separator
puts stdout $result
puts stdout $separator
}
if {[catch {
exec sdx wrap $buildfolder/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose
} result]} {
puts stderr "sdx wrap _build/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose failed with msg: $result"
} else {
puts stdout "ok - finished sdx"
set separator [string repeat = 40]
puts stdout $separator
puts stdout $result
puts stdout $separator
}
if {![file exists $buildfolder/$vfsname]} {
puts stderr "|err> build didn't seem to produce output at $sourcefolder/_build/$vfsname"
exit 2
}
if {![file exists $buildfolder/$vfsname]} {
puts stderr "|err> build didn't seem to produce output at $sourcefolder/_build/$vfsname"
exit 2
}
if {$::tcl_platform(platform) eq "windows"} {
set pscmd "tasklist"
# -- --- --- --- --- ---
set file_record [punkcheck::installfile_finished_install $basedir $file_record]
} else {
set pscmd "ps"
set skipped_vfs_build 1
puts stderr "."
puts stdout "Skipping build for vfs $vfs - no change detected"
set file_record [punkcheck::installfile_skipped_install $basedir $file_record]
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
if {!$skipped_vfs_build} {
if {$::tcl_platform(platform) eq "windows"} {
set pscmd "tasklist"
} else {
set pscmd "ps"
}
if {![catch {
exec $pscmd | grep $vfsname
} still_running]} {
puts stdout "found $vfsname instances still running\n"
set count_killed 0
foreach ln [split $still_running \n] {
puts stdout " $ln"
if {$::tcl_platform(platform) eq "windows"} {
set pid [lindex $ln 1]
if {$forcekill} {
set killcmd [list taskkill /F /PID $pid]
if {![catch {
exec $pscmd | grep $vfsname
} still_running]} {
puts stdout "found $vfsname instances still running\n"
set count_killed 0
foreach ln [split $still_running \n] {
puts stdout " $ln"
if {$::tcl_platform(platform) eq "windows"} {
set pid [lindex $ln 1]
if {$forcekill} {
set killcmd [list taskkill /F /PID $pid]
} else {
set killcmd [list taskkill /PID $pid]
}
} else {
set killcmd [list taskkill /PID $pid]
set pid [lindex $ln 0]
#review!
if {$forcekill} {
set killcmd [list kill -9 $pid]
} else {
set killcmd [list kill $pid]
}
}
} else {
set pid [lindex $ln 0]
#review!
if {$forcekill} {
set killcmd [list kill -9 $pid]
puts stdout " pid: $pid (attempting to kill now using '$killcmd')"
if {[catch {
exec {*}$killcmd
} errMsg]} {
puts stderr "$killcmd returned an error:"
puts stderr $errMsg
puts stderr "(try '[info script] -k' option to force kill)"
exit 4
} else {
set killcmd [list kill $pid]
puts stderr "$killcmd ran without error"
incr count_killed
}
}
puts stdout " pid: $pid (attempting to kill now using '$killcmd')"
if {[catch {
exec {*}$killcmd
} errMsg]} {
puts stderr "$killcmd returned an error:"
puts stderr $errMsg
puts stderr "(try '[info script] -k' option to force kill)"
exit 4
} else {
puts stderr "$killcmd ran without error"
incr count_killed
if {$count_killed > 0} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
}
} else {
puts stderr "Ok.. no running '$vfsname' processes found"
}
if {$count_killed > 0} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
}
} else {
puts stderr "Ok.. no running '$vfsname' processes found"
}
if {$::tcl_platform(platform) eq "windows"} {
set targetexe ${vfsname}.exe
} else {
set targetexe $vfsname
}
if {$::tcl_platform(platform) eq "windows"} {
set targetexe ${vfsname}.exe
} else {
set targetexe $vfsname
}
if {[file exists $buildfolder/$targetexe]} {
puts stderr "deleting existing $buildfolder/$targetexe"
if {[catch {
file delete $sourcefolder/_build/$targetexe
} msg]} {
puts stderr "Failed to delete $buildfolder/$targetexe"
exit 4
if {[file exists $buildfolder/$targetexe]} {
puts stderr "deleting existing $buildfolder/$targetexe"
if {[catch {
file delete $sourcefolder/_build/$targetexe
} msg]} {
puts stderr "Failed to delete $buildfolder/$targetexe"
exit 4
}
}
}
if {$::tcl_platform(platform) eq "windows"} {
file rename $buildfolder/$vfsname $sourcefolder/_build/${vfsname}.exe
}
if {$::tcl_platform(platform) eq "windows"} {
file rename $buildfolder/$vfsname $sourcefolder/_build/${vfsname}.exe
}
after 200
set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder
after 200
set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder
if {[file exists $deployment_folder/$targetexe]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetexe"
if {[catch {
file delete $deployment_folder/$targetexe
} errMsg]} {
puts stderr "deletion of deployed version at $deployment_folder/$targetexe failed: $errMsg"
exit 5
if {[file exists $deployment_folder/$targetexe]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetexe"
if {[catch {
file delete $deployment_folder/$targetexe
} errMsg]} {
puts stderr "deletion of deployed version at $deployment_folder/$targetexe failed: $errMsg"
exit 5
}
}
}
puts stdout "copying.."
puts stdout "$buildfolder/$targetexe"
puts stdout "to:"
puts stdout "$deployment_folder/$targetexe"
after 500
file copy $buildfolder/$targetexe $deployment_folder/$targetexe
puts stdout "copying.."
puts stdout "$buildfolder/$targetexe"
puts stdout "to:"
puts stdout "$deployment_folder/$targetexe"
after 500
file copy $buildfolder/$targetexe $deployment_folder/$targetexe
}
}
cd $startdir

1
src/modules/punk/mix/util-999999.0a1.0.tm

@ -208,6 +208,7 @@ namespace eval punk::mix::util {
return $dst
}
#namespace import ::punk::ns::nsimport_noclobber
proc namespace_import_pattern_to_namespace_noclobber {pattern ns} {
set source_ns [namespace qualifiers $pattern]

41
src/modules/punk/ns-999999.0a1.0.tm

@ -26,7 +26,7 @@ namespace eval ::punk_dynamic::ns {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::ns {
variable ns_current "::"
namespace export nsjoin nsprefix nstail nsparts nseval
namespace export nsjoin nsprefix nstail nsparts nseval nsimport_noclobber corp
#leading colon makes it hard (impossible?) to call directly if not within the namespace
#todo - change semantics of args - it's not particularly useful to pass namespaces as separated items - would be better to accept options (e.g nslist option -types)
@ -1268,6 +1268,45 @@ namespace eval punk::ns {
}
proc nsimport_noclobber {pattern {ns ""}} {
set source_ns [namespace qualifiers $pattern]
if {![namespace exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
if {$ns eq ""} {
set ns [uplevel 1 {namespace current}]
} elseif {![string match ::* $ns]} {
set nscaller [uplevel 1 {namespace current}]
set ns [punk::nsjoin $nscaller $ns]
}
set a_export_patterns [namespace eval $source_ns {namespace export}]
set a_commands [info commands $pattern]
set a_tails [lmap v $a_commands {namespace tail $v}]
set a_exported_tails [list]
foreach pattern $a_export_patterns {
set matches [lsearch -all -inline $a_tails $pattern]
foreach m $matches {
if {$m ni $a_exported_tails} {
lappend a_exported_tails $m
}
}
}
set imported_commands [list]
foreach e $a_exported_tails {
set imported [namespace eval $ns [string map [list <func> $e <a> $source_ns] {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
set cmd <func>
}
set cmd
}]]
if {[string length $imported]} {
lappend imported_commands $imported
}
}
return $imported_commands
}
interp alias {} nsthis {} punk::ns::nspath_here_absolute
interp alias {} nsorigin {} apply {ns {namespace origin [uplevel 1 ::punk::ns::nspath_here_absolute $ns]} ::}
interp alias {} nsvars {} punk::ns::nsvars

12
src/modules/punk/repl-0.1.tm

@ -850,6 +850,7 @@ proc repl::rputs {args} {
set rputschan [lindex $args 1]
}
set last_char_info_width 40
#review - string shouldn't be truncated prior to stripcodes - could chop ansi codes!
set summary "[::shellfilter::ansi::stripcodes [string range $out 0 $last_char_info_width]]"
if {[string length $out] > $last_char_info_width} {
append summary " ..."
@ -971,7 +972,8 @@ proc repl::repl_handler {inputchan prompt_config} {
}
set [namespace current]::done 1
#test
tailcall repl::reopen_stdin
#JMN
#tailcall repl::reopen_stdin
}
}
set resultprompt [dict get $prompt_config resultprompt]
@ -1110,8 +1112,8 @@ proc repl::repl_handler {inputchan prompt_config} {
}
#-----------------------------------------
set lastoutchar [string index $::repl::output_stdout end]
set lasterrchar [string index $::repl::output_stderr end]
set lastoutchar [string index [overtype::stripansi $::repl::output_stdout] end]
set lasterrchar [string index [overtype::stripansi $::repl::output_stderr] end]
#to determine whether cursor is back at col0 of newline
screen_last_char_add [string index $lastoutchar$lasterrchar end] "stdout/stderr"
@ -1239,7 +1241,9 @@ proc repl::repl_handler {inputchan prompt_config} {
} elseif {$termchan eq "info"} {
rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text]
} else {
rputs -nonewline $termchan $text
#rputs -nonewline $termchan $text
set chanprompt "_ "
rputs $termchan ${chanprompt}[string map [list \n "\n${chanprompt}"] $text]
}
}
}

30
src/modules/shellfilter-0.1.8.tm

@ -165,7 +165,7 @@ namespace eval shellfilter::ansi {
#return "\x1b\[0m" ;#reset color only
}
#maintenance warning - also in 'textblock' pkg
#maintenance warning - also in 'overtype' pkg
#strip ansi codes from text - basic! assumes we don't get data split in the middle of an ansi-code ie best used with line-buffering
proc stripcodes {text} {
if {[set posn [string first "\033\[" $text]] >= 0} {
@ -500,6 +500,8 @@ namespace eval shellfilter::chan {
}
}
#review - we should probably provide a more narrow filter than only strips color - and one that strips most(?)
# - but does it ever really make sense to strip things like "esc(0" and "esc(B" which flip to the G0 G1 characters? (once stripped - things like box-lines become ordinary letters - unlikely to be desired?)
#assumes line-buffering. a more advanced filter required if ansicodes can arrive split accross separate read or write operations!
oo::class create ansistrip {
variable o_trecord
@ -1262,7 +1264,9 @@ namespace eval shellfilter::stack {
variable pipelines
set stack [dict get $pipelines $pipename stack]
set tag "SHELLFILTER::STACK"
::shellfilter::log::open $tag {-syslog 127.0.0.1:514}
#JMN - load from config
#::shellfilter::log::open $tag {-syslog 127.0.0.1:514}
::shellfilter::log::open $tag {-syslog ""}
::shellfilter::log::write $tag "transform stack for $pipename $args"
foreach tf $stack {
::shellfilter::log::write $tag " $tf"
@ -1799,13 +1803,17 @@ namespace eval shellfilter {
-inchan stdin \
-tclscript 0 \
]
set opts [dict merge $defaults $args]
set outchan [dict get $opts -outchan]
set errchan [dict get $opts -errchan]
set inchan [dict get $opts -inchan]
set teehandle [dict get $opts -teehandle]
set is_script [dict get $opts -tclscript]
set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set outchan [dict get $opts -outchan]
set errchan [dict get $opts -errchan]
set inchan [dict get $opts -inchan]
set teehandle [dict get $opts -teehandle]
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set is_script [dict get $opts -tclscript]
dict unset opts -tclscript ;#don't pass it any further
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set teehandle_out ${teehandle}out ;#default commandout
set teehandle_err ${teehandle}err
set teehandle_in ${teehandle}in
@ -1876,7 +1884,7 @@ namespace eval shellfilter {
#script result
set exitinfo [list result [uplevel #0 [list eval $commandlist]]]
} errMsg]} {
set exitinfo [list error "$errMsg" errorInfo "$::errorInfo"]
set exitinfo [list error "$errMsg" errorCode $::errorCode errorInfo "$::errorInfo"]
}
}
@ -1955,7 +1963,9 @@ namespace eval shellfilter {
]
set runtag shellfilter-run2
set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]]
#JMN - load from config
#set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]]
set tid [::shellfilter::log::open $runtag [list -syslog ""]]
if {([llength $args] % 2) != 0} {
error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'"

92
src/modules/shellrun-0.1.tm

@ -191,18 +191,24 @@ namespace eval shellrun {
shellfilter::stack::remove stderr $stderr_stackid
#shellfilter::stack::remove commandout $outvar_stackid
if {[dict exists $exitinfo error]} {
#we must raise an error.
#todo - check errorInfo makes sense.. return -code? tailcall?
#
set msg ""
append msg [dict get $exitinfo error]
append msg "\n(add -tcl option to run as a tcl command/script instead of an external command)"
error $msg
if {"-tcl" in $runopts} {
} else {
#we must raise an error.
#todo - check errorInfo makes sense.. return -code? tailcall?
#
set msg ""
append msg [dict get $exitinfo error]
append msg "\n(add -tcl option to run as a tcl command/script instead of an external command)"
error $msg
}
}
set chunklist [list]
#exitcode not part of return value for runout - colourcode appropriately
set n [a+]
set c ""
if [dict exists $exitinfo exitcode] {
@ -212,11 +218,18 @@ namespace eval shellrun {
} else {
set c [a+ white bold]
}
lappend chunklist [list "info" "$c$exitinfo$n"]
} elseif [dict exists $exitinfo error] {
set c [a+ yellow bold]
lappend chunklist [list "info" "${c}error [dict get $exitinfo error]$n"]
lappend chunklist [list "info" "errorCode [dict get $exitinfo errorCode]"]
#lappend chunklist [list "info" "errorInfo [list [dict get $exitinfo errorInfo]]"]
lappend chunklist [list "info" errorInfo]
lappend chunklist [list "stderr" [dict get $exitinfo errorInfo]]
} else {
set c [a+ Yellow red bold]
lappend chunklist [list "info" "$c$exitinfo$n"]
}
#exitcode not part of return value for runout - colourcode appropriately
lappend chunklist [list "info" "$c$exitinfo$n"]
set chunk "[a+ red bold]stderr[a+]"
@ -229,7 +242,8 @@ namespace eval shellrun {
} else {
set e $::shellrun::runerr
}
append chunk "[a+ red light]$e[a+]\n"
#append chunk "[a+ red light]$e[a+]\n"
append chunk "[a+ red light]$e[a+]"
}
lappend chunklist [list stderr $chunk]
@ -244,7 +258,7 @@ namespace eval shellrun {
} else {
set o $::shellrun::runout
}
append chunk "$o" ;#this newline is the display output separator - always there whether data has trailing newline or not.
append chunk "$o"
}
lappend chunklist [list result $chunk]
@ -299,8 +313,12 @@ namespace eval shellrun {
#we raise an error because an error during calling is different to collecting stderr from a command, and the caller should be able to wrap in a catch
# to determine something other than just a nonzero exit code or output on stderr.
if {[dict exists $exitinfo error]} {
#todo - check errorInfo makes sense.. return -code? tailcall?
error [dict get $exitinfo error]
if {"-tcl" in $runopts} {
} else {
#todo - check errorInfo makes sense.. return -code? tailcall?
error [dict get $exitinfo error]
}
}
set chunklist [list]
@ -314,11 +332,18 @@ namespace eval shellrun {
} else {
set c [a+ white bold]
}
lappend chunklist [list "info" "$c$exitinfo$n"]
} elseif [dict exists $exitinfo error] {
set c [a+ yellow bold]
lappend chunklist [list "info" "error [dict get $exitinfo error]"]
lappend chunklist [list "info" "errorCode [dict get $exitinfo errorCode]"]
lappend chunklist [list "info" "errorInfo [list [dict get $exitinfo errorInfo]]"]
} else {
set c [a+ Yellow red bold]
lappend chunklist [list "info" "$c$exitinfo$n"]
}
#exitcode not part of return value for runout - colourcode appropriately
lappend chunklist [list "info" "$c$exitinfo$n"]
lappend chunklist [list "info" "[a+ white bold]stdout[a+]"]
@ -408,8 +433,12 @@ namespace eval shellrun {
flush stdout
if {[dict exists $exitinfo error]} {
#todo - check errorInfo makes sense.. return -code? tailcall?
error [dict get $exitinfo error]
if {"-tcl" in $runopts} {
} else {
#todo - check errorInfo makes sense.. return -code? tailcall?
error [dict get $exitinfo error]
}
}
#set x [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::repl::runxoutput}]
@ -425,9 +454,9 @@ namespace eval shellrun {
}
set chunklist [list]
lappend chunklist [list "info" " "]
lappend chunklist [list "result" stdout]
lappend chunklist [list "result" stdout] ;#key 'stdout' forms part of the resulting dictionary output
lappend chunklist [list "info" "[a+ white bold]stdout[a+]"]
lappend chunklist [list result $chunk]
lappend chunklist [list result $chunk] ;#value corresponding to 'stdout' key in resulting dict
lappend chunklist [list "info" " "]
@ -451,7 +480,7 @@ namespace eval shellrun {
set n [a+]
set c ""
if [dict exists $exitinfo exitcode] {
if {[dict exists $exitinfo exitcode]} {
set code [dict get $exitinfo exitcode]
if {$code == 0} {
set c [a+ green]
@ -463,14 +492,33 @@ namespace eval shellrun {
lappend chunklist [list "info" "exitcode $code"]
lappend chunklist [list "result" "$c$code$n"]
set exitdict [list exitcode $code]
} elseif [[dict exists $exitinfo result]] {
} elseif {[dict exists $exitinfo result]} {
# presumably from a -tcl call
set val [dict get $exitinfo result]
lappend chunklist [list "info" " "]
lappend chunklist [list "result" result]
lappend chunklist [list "info" result]
lappend chunklist [list "info" result]
lappend chunklist [list "result" $val]
set exitdict [list result $val]
} elseif {[dict exists $exitinfo error]} {
# -tcl call with error
#set exitdict [dict create]
lappend chunklist [list "info" " "]
lappend chunklist [list "result" error]
lappend chunklist [list "info" error]
lappend chunklist [list "result" [dict get $exitinfo error]]
lappend chunklist [list "info" " "]
lappend chunklist [list "result" errorCode]
lappend chunklist [list "info" errorCode]
lappend chunklist [list "result" [dict get $exitinfo errorCode]]
lappend chunklist [list "info" " "]
lappend chunklist [list "result" errorInfo]
lappend chunklist [list "info" errorInfo]
lappend chunklist [list "result" [dict get $exitinfo errorInfo]]
set exitdict $exitinfo
} else {
#review - if no exitcode or result. then what is it?
lappend chunklist [list "info" exitinfo]

80
src/modules/textblock-999999.0a1.0.tm

@ -20,17 +20,38 @@
package require punk
package require patternpunk
package require overtype
package require term::ansi::code::macros
package require textutil
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval textblock {
namespace eval cd {
#todo - save and restore existing namespace export in case macros::cd has default exports in future
namespace eval ::term::ansi::code::macros::cd {namespace export *}
namespace import ::term::ansi::code::macros::cd::*
namespace eval ::term::ansi::code::macros::cd {namespace export -clear}
}
#must be able to handle block as string with or without newlines
#if no newlines - attempt to treat as a list
#must handle whitespace-only string,list elements, and/or lines.
proc width {block} {
if {![llength $block]} {
return [string length [stripcodes $block]]
if {$block eq ""} {
return 0
}
set block [textutil::tabify::untabify2 $block]
if {[string first \n $block] >= 0} {
return [tcl::mathfunc::max {*}[lmap v [linelist $block] {string length [stripansi $v]}]]
}
if {[catch {llength $block}]} {
return [string length [stripansi $block]]
}
tcl::mathfunc::max {*}[lmap v [linelist $block] {string length [stripcodes $v]}]
if {[llength $block] == 0} {
#could be just a whitespace string
return [string length $block]
}
return [tcl::mathfunc::max {*}[lmap v $block {string length [stripansi $v]}]]
}
pipealias ::textblock::padleft .= {list $input [string repeat " " $indent]} |/0,padding/1> linelist |> .= {lmap v $data {val "$padding$v"}} |> list_as_lines <input/0,indent/1|
@ -38,25 +59,25 @@ namespace eval textblock {
pipealias ::textblock::join_width .= {list $lhs [string repeat " " $w1] $rhs [string repeat " " $w2]} {|
/2,col1/1,col2/3
>} linelist {|
>} .=>1 linelist -block {} {|
data2
>} .=lhs> linelist {|
>} .=lhs>1 linelist -block {} {|
>} .= {lmap v $data w $data2 {val "[overtype::left $col1 $v][overtype::left $col2 $w]"}} {|
>} list_as_lines <lhs/0,w1/1,rhs/2,w2/3|
pipealias ::textblock::join .= {list $lhs [string repeat " " [width $lhs]] $rhs [string repeat " " [width $rhs]]} {|
/2,col1/1,col2/3
>} linelist {|
>} .=>1 linelist -block {} {|
data2
>} .=lhs> linelist {|
>} .=lhs>1 linelist -block {} {|
>} .= {lmap v $data w $data2 {val "[overtype::left $col1 $v][overtype::left $col2 $w]"}} {|
>} list_as_lines <lhs/0,rhs/1|
pipealias ::textblock::join_right .= {list $lhs [string repeat " " [width $lhs]] $rhs [string repeat " " [width $rhs]]} {|
/2,col1/1,col2/3
>} linelist {|
>} .=>1 linelist -block {} {|
data2
>} .=lhs> linelist {|
>} .=lhs>1 linelist -block {} {|
>} .= {lmap v $data w $data2 {val "[overtype::right $col1 $v][overtype::right $col2 $w]"}} {|
>} list_as_lines <lhs/0,rhs/1|
@ -65,39 +86,30 @@ namespace eval textblock {
.= textblock::join [list 1 2 3 4 5 6 7] [>punk . lhs] |> .=>1 textblock::join " " |> .=>1 textblock::join $text |> .=>1 textblock::join [>punk . rhs] |> .=>1 textblock::join [lrepeat 7 " | "]
}
#maintenance warning - also in 'shellfilter' pkg
#strip ansi codes from text - basic! assumes we don't get data split in the middle of an ansi-code ie best used with line-buffering
proc stripcodes {text} {
if {[set posn [string first "\033\[" $text]] >= 0} {
set mnext [string first m [string range $text $posn end]]
if {$mnext >= 0} {
set mpos [expr {$posn + $mnext}]
set stripped1 [string range $text 0 $posn-1][string range $text $mpos+1 end]
#return [stripcodes $stripped1] ;#recurse to get any others
tailcall ::textblock::stripcodes $stripped1
} else {
#partial or not actually an ansi code.. pass it all through
return $text
}
proc frame {string args} {
#todo args -justify left|centre|right (center)
set string [textutil::tabify::untabify2 $string]
set string [string map [list \r\n \n] $string]
if {[string first \n $string] >= 0} {
set width [width $string]
} else {
return $text
set width [width [list $string]]
}
set lines [split $string \n]
append fs [cd::tlc][string repeat [cd::hl] $width][cd::trc]\n
foreach l $lines {
append fs [cd::vl]${l}[string repeat " " [expr {$width-[string length [stripansi $l]]}]][cd::vl]\n
}
append fs [cd::blc][string repeat [cd::hl] $width][cd::brc]
return [cd::groptim $fs]
}
namespace import ::overtype::stripansi
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide textblock [namespace eval textblock {

73
src/punk86.vfs/lib/app-shellspy/shellspy.tcl

@ -338,6 +338,7 @@ namespace eval shellspy {
shellfilter::log::write $shellspy_status_log "do_in_powershell returning $exitinfo"
#exit [lindex $exitinfo 1]
}
return $exitinfo
}
proc do_in_powershell_terminal {args} {
variable shellspy_status_log
@ -361,6 +362,7 @@ namespace eval shellspy {
if {[lindex $exitinfo 0] eq "exitcode"} {
shellfilter::log::write $shellspy_status_log "do_in_powershell_terminal returning $exitinfo"
}
return $exitinfo
}
@ -394,6 +396,7 @@ namespace eval shellspy {
shellfilter::log::write $shellspy_status_log "do_in_cmdshell returning $exitinfo"
#puts stderr "do_in_cmdshell returning $exitinfo"
}
return $exitinfo
}
proc do_in_cmdshellb {args} {
@ -426,6 +429,7 @@ namespace eval shellspy {
} else {
shellfilter::log::write $shellspy_status_log "do_in_cmdshellb returning WITHOUT exitcode $exitinfo"
}
return $exitinfo
}
proc do_in_cmdshelluc {args} {
variable shellspy_status_log
@ -453,6 +457,7 @@ namespace eval shellspy {
shellfilter::log::write $shellspy_status_log "do_in_cmdshelluc returning $exitinfo"
#puts stderr "do_in_cmdshell returning $exitinfo"
}
return $exitinfo
}
proc do_raw {args} {
variable shellspy_status_log
@ -476,6 +481,7 @@ namespace eval shellspy {
if {[lindex $exitinfo 0] eq "exitcode"} {
shellfilter::log::write $shellspy_status_log "do_raw returning $exitinfo"
}
return $exitinfo
}
proc do_script_process {scriptbin scriptname args} {
@ -517,6 +523,10 @@ namespace eval shellspy {
if {[lindex $exitinfo 0] eq "exitcode"} {
shellfilter::log::write $shellspy_status_log "do_script_process returning $exitinfo"
}
if {[dict exists $exitinfo errorCode]} {
exit [dict get $exitinfo $errorCode]
}
return $exitinfo
}
proc do_script {scriptname replwhen args} {
#ideally we don't want to launch an external process to run the script
@ -524,7 +534,12 @@ namespace eval shellspy {
shellfilter::log::write $shellspy_status_log "do_script got scriptname:'$scriptname' replwhen:$replwhen args:'$args'"
set exedir [file dirname [info nameofexecutable]]
set libroot [file join $exedir scriptlib]
if {[file tail $exedir] eq "bin"} {
set basedir [file dirname $exedir]
} else {
set basedir $exedir
}
set libroot [file join $basedir scriptlib]
if {[string match lib::* $scriptname]} {
set scriptname [string map [list "lib::" "" "::" "/"] $scriptname]
set scriptpath $libroot/$scriptname
@ -538,7 +553,7 @@ namespace eval shellspy {
error "bad scriptpath '$scriptpath'"
}
}
set modulesdir $exedir/modules
set modulesdir $basedir/modules
set script [string map [list %a% $args %s% $scriptpath %m% $modulesdir] {
::tcl::tm::add %m%
@ -582,9 +597,12 @@ source [file normalize $scriptname]
shellfilter::stack::remove stderr $id_err
if {[lindex $exitinfo 0] eq "exitcode"} {
shellfilter::log::write $shellspy_status_log "do_script returning $exitinfo"
}
#if {[lindex $exitinfo 0] eq "exitcode"} {
# shellfilter::log::write $shellspy_status_log "do_script returning $exitinfo"
#}
shellfilter::log::write $shellspy_status_log "do_script returning $exitinfo"
return $exitinfo
}
proc shellescape {arglist} {
@ -618,6 +636,7 @@ source [file normalize $scriptname]
if {[lindex $exitinfo 0] eq "exitcode"} {
shellfilter::log::write $shellspy_status_log "do_shell $shell returning $exitinfo"
}
return $exitinfo
}
proc do_wsl {dist args} {
variable shellspy_status_log
@ -645,6 +664,7 @@ source [file normalize $scriptname]
if {[lindex $exitinfo 0] eq "exitcode"} {
shellfilter::log::write $shellspy_status_log "do_wsl $dist returning $exitinfo"
}
return $exitinfo
}
#todo - load these from a callback
@ -659,89 +679,89 @@ source [file normalize $scriptname]
#we may get ambiguity with existing shell match-specs such as -c /c -r. todo - only match those in first slot?
lappend commands [list tclscriptprocess [list match [list .*\.TCL$ .*\.TM$ .*\.TK$] dispatch [list shellspy::do_script_process tclsh %matched%] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list tclscriptprocess [list sub word$i singleopts {any}]]
lappend commands [list tclscriptprocess [list sub word$i singleopts {any}]]
}
#camelcase convention .Tcl script before repl
lappend commands [list tclscriptbeforerepl [list match [list .*\.Tcl$ .*\.Tm$ .*\.Tk$ ] dispatch [list shellspy::do_script %matched% "repl_last"] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list tclscriptbeforerepl [list sub word$i singleopts {any}]]
lappend commands [list tclscriptbeforerepl [list sub word$i singleopts {any}]]
}
#Backwards Camelcase convention .tcL - means repl first, script last
lappend commands [list tclscriptafterrepl [list match [list .*\.tcL$ .*\.tM$ .*\.tK$ ] dispatch [list shellspy::do_script %matched% "repl_first"] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list tclscriptafterrepl [list sub word$i singleopts {any}]]
lappend commands [list tclscriptafterrepl [list sub word$i singleopts {any}]]
}
#we've already handled .Tcl .tcL, .tCl, .TCL - handle any other capitalisations as a script in this process
lappend commands [list tclscript [list match [list .*\.tcl$ .*\.tCL$ .*\.TCl$ .*\.tm$ .*\.tk$ ] dispatch [list shellspy::do_script %matched% "no_repl"] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list tclscript [list sub word$i singleopts {any}]]
lappend commands [list tclscript [list sub word$i singleopts {any}]]
}
lappend commands [list luascriptprocess [list match [list .*\.lua|Lua|LUA$] dispatch [list shellspy::do_script_process lua %matched% "no_repl"] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list luascriptprocess [list sub word$i singleopts {any}]]
lappend commands [list luascriptprocess [list sub word$i singleopts {any}]]
}
lappend commands [list phpscriptprocess [list match [list .*\.php|Php|PHP$] dispatch [list shellspy::do_script_process php %matched% "no_repl"] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list phpscriptprocess [list sub word$i singleopts {any}]]
lappend commands [list phpscriptprocess [list sub word$i singleopts {any}]]
}
lappend commands [list bashraw [list match ^bash$ dispatch [list shellspy::do_shell bash] dispatchtype raw dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list bashraw [list sub word$i singleopts {any}]]
lappend commands [list bashraw [list sub word$i singleopts {any}]]
}
lappend commands {shraw {match ^sh$ dispatch {shellspy::do_shell sh} dispatchtype raw dispatchglobal 1 singleopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list shraw [list sub word$i singleopts {any}]]
lappend commands [list shraw [list sub word$i singleopts {any}]]
}
lappend commands [list runbash [list match ^b$ dispatch [list shellspy::do_shell bash] dispatchtype shell dispatchglobal 1 singleopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runbash [list sub word$i singleopts {any}]]
lappend commands [list runbash [list sub word$i singleopts {any}]]
}
lappend commands {runsh {match ^s$ dispatch {shellspy::do_shell sh} dispatchtype shell dispatchglobal 1 singleopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runsh [list sub word$i singleopts {any}]]
lappend commands [list runsh [list sub word$i singleopts {any}]]
}
lappend commands {runraw {match ^-r$ dispatch shellspy::do_raw dispatchtype raw dispatchglobal 1 singleopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runraw [list sub word$i singleopts {any}]]
lappend commands [list runraw [list sub word$i singleopts {any}]]
}
lappend commands {runpwsh {match ^-c$ dispatch shellspy::do_in_powershell dispatchtype raw dispatchglobal 1 singleopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runpwsh [list sub word$i singleopts {any}]]
lappend commands [list runpwsh [list sub word$i singleopts {any}]]
}
lappend commands {runpwsht {match ^pwsh$ dispatch shellspy::do_in_powershell_terminal dispatchtype raw dispatchglobal 1 singleopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runpwsht [list sub word$i singleopts {any}]]
lappend commands [list runpwsht [list sub word$i singleopts {any}]]
}
lappend commands {runcmd {match ^/c$ dispatch shellspy::do_in_cmdshell dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runcmd [list sub word$i singleopts {any}]]
lappend commands [list runcmd [list sub word$i singleopts {any}]]
}
lappend commands {runcmduc {match ^/u/c$ dispatch shellspy::do_in_cmdshelluc dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any}}}
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runcmduc [list sub word$i singleopts {any}]]
lappend commands [list runcmduc [list sub word$i singleopts {any}]]
}
#cmd with bracked args () e.g with vim shellxquote set to "("
lappend commands [list runcmdb [list match ^cmdb$ dispatch [list shellspy::do_in_cmdshellb %matched%] dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any} pairopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list runcmdb [list sub word$i singleopts {any} longopts {any} pairopts {any}]]
lappend commands [list runcmdb [list sub word$i singleopts {any} longopts {any} pairopts {any}]]
}
lappend commands [list wslraw [list match ^wsl$ dispatch [list shellspy::do_wsl AlmaLinux9] dispatchtype raw dispatchglobal 1 singleopts {any} longopts {any}]]
for {set i 0} {$i < 25} {incr i} {
lappend commands [list wslraw [list sub word$i singleopts {any}]]
lappend commands [list wslraw [list sub word$i singleopts {any}]]
}
@ -780,8 +800,9 @@ source [file normalize $scriptname]
puts -nonewline stderr "|shellspy-stderr> $errMsg\n"
puts -nonewline stderr "|shellspy-stderr> [set ::errorInfo]\n"
shellfilter::log::write $shellspy_status_log "check_flags error: $errMsg"
} else {
puts stdout "shellspy final-arglist $arglist"
shellfilter::log::write $shellspy_status_log "check_flags result: $arglist"
}
shellfilter::log::write $shellspy_status_log "check_flags dispatch -done-"
@ -798,11 +819,9 @@ source [file normalize $scriptname]
}
#don't open more logs..
#puts stdout ">$tidyinfo"
#shellfilter::log::write $shellspy_status_log "logtidyup -done- $tidyinfo"
#shellfilter::log::write $shellspy_status_log "logtidyup -done- $tidyinfo"
set errorlist [dict get $tidyinfo errors]
if {[llength $errorlist]} {

632
src/vendormodules/overtype-1.5.0.tm

@ -0,0 +1,632 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) Julian Noble 2003-2023
#
# @@ Meta Begin
# Application overtype 1.5.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
package require textutil
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#Julian Noble <julian@precisium.com.au> - 2003
#Released under standard 'BSD license' conditions.
#
#todo - ellipsis truncation indicator for center,right
#v1.4 2023-07 - naive ansi color handling - todo - fix string range
# - need to extract and replace ansi codes?
namespace eval overtype {
namespace export *
variable default_ellipsis_horizontal "..." ;#fallback
variable default_ellipsis_vertical "..."
namespace eval priv {
proc _init {} {
upvar ::overtype::default_ellipsis_horizontal e_h
upvar ::overtype::default_ellipsis_vertical e_v
set e_h [format %c 0x2026] ;#Unicode Horizontal Ellipsis
set e_v [format %c 0x22EE]
#The unicode ellipsis looks more natural than triple-dash which is centred vertically whereas ellipsis is at floorline of text
#Also - unicode ellipsis has semantic meaning that other processors can interpret
#unicode does also provide a midline horizontal ellipsis 0x22EF
#set e [format %c 0x2504] ;#punk::char::charshort boxd_ltdshhz - Box Drawings Light Triple Dash Horizontal
#if {![catch {package require punk::char}]} {
# set e [punk::char::charshort boxd_ltdshhz]
#}
}
}
priv::_init
}
proc overtype::about {} {
return "Simple text formatting. Author JMN. BSD-License"
}
#interp alias {} ::overtype::stripcodes {} ::overtype::stripansi
proc overtype::stripcodes {text} {
tailcall overtype::stripansi $text
}
proc overtype::stripansi {text} {
set inputlist [split $text ""]
set outputlist [list]
dict set escape_terminals 1 [list J K m n A B C D E F G s u] ;#review
dict set escape_terminals 2 [list \007]
set in_escapesequence 0
#assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls
set i 0
foreach u $inputlist {
set v [lindex $inputlist $i+1]
if {$in_escapesequence != 0} {
if {$u in [dict get $escape_terminals $in_escapesequence]} {
set in_escapesequence 0
}
} else {
if {$u eq "\033" && $v eq "\["} {
set in_escapesequence 1
} elseif {$u eq "\033" && $v eq "\]"} {
set in_escapesequence 2
} else {
lappend outputlist $u
}
}
incr i
}
return [join $outputlist ""]
}
#maintenance warning - also in 'shellfilter' pkg
#strip ansi codes from text - basic! assumes we don't get data split in the middle of an ansi-code ie best used with line-buffering
proc overtype::stripansi2 {text} {
variable lasttext ""
if {[set posn [string first "\033" $text]] < 0} {
return $text
}
if {[set posn [string first "\033(" $text]] >= 0} {
#e.g "\033(0" - select VT100 graphics for character set G0
#e.g "\033(X" - where X is any char other than 0 to reset
set text [string range $text 0 $posn-1][string range $text $posn+3 end]
}
if {[set posn [string first "\033)" $text]] >= 0} {
#e.g "\033)0" - select VT100 graphics for character set G1
#e.g "\033)X" - where X is any char other than 0 to reset
set text [string range $text 0 $posn-1][string range $text $posn+3 end]
}
#strip title including inner text - the title is an instruction to the window - so the title-text doesn't form part of the line-data in the text block
if {[set posn [string first "\033\]" $text]] >= 0} {
set next2 [string range $text $posn+2 $posn+3]
if {$next2 in [list {0;} {1;} {2;}]} {
#0; set icon name and window title
#1; set icon name
#2; set window title
set tail [string range $text $posn end]
set endoffset [string first \007 $tail]
if {$endoffset > 3} {
set text [string range $text 0 $posn-1][string range $text $posn+$offset+1 end]
} else {
#unexpected.. we seem to be missing terminating BEL character
puts stderr "stripansi failure: missing terminator BEL on title"
}
}
}
if {[set posn [string first "\033\[" $text]] >= 0} {
set mnext [string first m [string range $text $posn end]]
if {$mnext >= 0} {
set mpos [expr {$posn + $mnext}]
set stripped1 [string range $text 0 $posn-1][string range $text $mpos+1 end]
#return [stripansi $stripped1] ;#recurse to get any others
tailcall ::overtype::stripansi $stripped1
} else {
#partial or not actually a basic ansi code.. pass it all through
return $text
}
}
if {[set posn [string first "\033" $text]] >= 0} {
if {$lasttext eq $text} {
return $text
}
set lasttext $text
tailcall ::overtype::stripansi $text
}
return $text
}
proc overtype::strip_nonprinting {str} {
set map [list\
\007 ""\
[format %c 0] ""\
[format %c 0x7f] ""\
]
return [string map $map $str]
}
#length of text for printing characters only
#review - unicode and other non-printing chars?
#review - is there an existing library or better method? print to a terminal and query cursor position?
#Note this length calculation is only suitable for lines being appended to other strings if the line is pre-processed to account for backspace and carriage returns first
#If the raw line is appended to another string without such processing - the backspaces & carriage returns can affect data prior to the start of the string.
proc overtype::printing_length {line} {
if {[string first \n $line] >= 0} {
error "line_print_length must not contain newline characters"
}
set line [stripansi $line]
set line [strip_nonprinting $line] ;#only strip nonprinting after stripansi - some like BEL are part of ansi
#backspace 0x08 only erases* printing characters anyway - so presumably order of processing doesn't matter
#(* more correctly - moves cursor back)
#backspace will not move beyond a preceding newline - but we have disallowed newlines for this function already
#leading backspaces will eat into any string (even prompt in non-standard tclsh shell) that is prepended to the line
# - but for the purposes of overtype we wouldn't want that - so we strip it here in the length calculation and should strip leading backspaces in the actual data concatenation operations too.
#curiously - a backspace sequence at the end of a string also doesn't reduce the printing width - so we can also strip from RHS
#Note that backspace following a \t will only shorten the string by one (ie it doesn't move back the whole tab width like it does interactively in the terminal)
#for this reason - it would seem best to normalize the tabs to spaces prior to performing the backspace calculation - otherwise we won't account for the 'short' tabs it effectivley produces
#normalize tabs to an appropriate* width
#*todo - handle terminal/context where tabwidth != the default 8 spaces
set line [textutil::tabify::untabify2 $line]
set bs [format %c 0x08]
#set line [string map [list "\r${bs}" "\r"] $line] ;#backsp following a \r will have no effect
set line [string trim $line $bs]
set n 0
set chars [split $line ""]
#build an output
set idx 0
set out [list]
foreach c $chars {
if {$c eq $bs} {
if {$idx > 0} {
incr idx -1
}
} elseif {$c eq "\r"} {
set idx 0
} else {
priv::printing_length_addchar $idx $c
incr idx
}
}
set line2 [join $out ""]
return [string length $line2]
}
namespace eval overtype::priv {
proc printing_length_addchar {i c} {
upvar out o
set nxt [llength $o]
if {$i < $nxt} {
lset o $i $c
} else {
lappend o $c
}
}
}
#string range should generally be avoided for both undertext and overtext which contain ansi escapes and other cursor affecting chars such as \b and \r
proc overtype::left {args} {
# @c overtype starting at left (overstrike)
# @c can/should we use something like this?: 'format "%-*s" $len $overtext
variable default_ellipsis_horizontal
if {[llength $args] < 2} {
error {usage: ?-transparent [0|1]? ?-overflow [1|0]? ?-ellipsis [1|0]? ?-ellipsistext ...? undertext overtext}
}
foreach {underblock overblock} [lrange $args end-1 end] break
set defaults [dict create\
-ellipsis 0\
-ellipsistext $default_ellipsis_horizontal\
-overflow 0\
-transparent 0\
]
set known_opts [dict keys $defaults]
set argsflags [lrange $args 0 end-2]
dict for {k v} $argsflags {
if {$k ni $known_opts} {
error "overtype::left unknown option '$k'. Known options: $known_opts"
}
}
set opts [dict merge $defaults $argsflags]
# -- --- --- --- --- ---
set opt_transparent [dict get $opts -transparent]
set opt_ellipsistext [dict get $opts -ellipsistext]
# -- --- --- --- --- ---
set norm [list \r\n \n]
set underblock [string map $norm $underblock]
set overblock [string map $norm $overblock]
set underlines [split $underblock \n]
set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {printing_length $v}]]
set overlines [split $overblock \n]
set outputlines [list]
foreach undertext $underlines overtext $overlines {
set undertext_printlen [printing_length $undertext]
set overlen [printing_length $overtext]
set diff [expr {$overlen - $colwidth}]
#review
append overtext "\033\[0m"
if {$diff > 0} {
#background line is narrower
set rendered [renderline -transparent $opt_transparent -overflow [dict get $opts -overflow] $undertext $overtext]
if {![dict get $opts -overflow]} {
#set overtext [string range $overtext 0 $colwidth-1] ;#string range won't be correct e.g if contains ansi codes or leading \r or \b etc
if {[dict get $opts -ellipsis]} {
set rendered [overtype::right $rendered $opt_ellipsistext]
}
}
lappend outputlines $rendered
} else {
#we know overtext is shorter or equal
lappend outputlines [renderline -transparent $opt_transparent $undertext $overtext]
}
}
return [join $outputlines \n]
}
#todo - left-right ellipsis ?
proc overtype::centre {args} {
variable default_ellipsis_horizontal
if {[llength $args] < 2} {
error {usage: ?-transparent [0|1]? ?-bias [left|right]? ?-overflow [1|0]? undertext overtext}
}
foreach {underblock overblock} [lrange $args end-1 end] break
set defaults [dict create\
-bias left\
-ellipsis 0\
-ellipsistext $default_ellipsis_horizontal\
-overflow 0\
-transparent 0\
]
set known_opts [dict keys $defaults]
set argsflags [lrange $args 0 end-2]
dict for {k v} $argsflags {
if {$k ni $known_opts} {
error "overtype::centre unknown option '$k'. Known options: $known_opts"
}
}
set opts [dict merge $defaults $argsflags]
# -- --- --- --- --- ---
set opt_transparent [dict get $opts -transparent]
set opt_ellipsistext [dict get $opts -ellipsistext]
# -- --- --- --- --- ---
set norm [list \r\n \n]
set underblock [string map $norm $underblock]
set overblock [string map $norm $overblock]
set underlines [split $underblock \n]
set colwidth [tcl::mathfunc::max {*}[lmap v $underlines {printing_length $v}]]
set overlines [split $overblock \n]
set outputlines [list]
foreach undertext $underlines overtext $overlines {
set olen [printing_length $overtext]
set ulen [printing_length $undertext]
if {$ulen < $colwidth} {
set udiff [expr {$colwidth - $ulen}]
set undertext "$undertext[string repeat { } $udiff]"
}
#review
append overtext "\033\[0m"
set diff [expr {$colwidth - $olen}]
if {$diff > 0} {
#background block is wider
set half [expr {round(int($diff / 2))}]
if {[string match right [dict get $opts -bias]]} {
if {[expr {2 * $half}] < $diff} {
incr half
}
}
set rhs [expr {$diff - $half - 1}]
set lhs [expr {$half - 1}]
set a [string range $undertext 0 $lhs]
set rhsoffset [expr {$rhs +1}]
set background [string range $undertext $lhs+1 end-$rhsoffset]
set b [renderline -transparent $opt_transparent $background $overtext]
set c [string range $undertext end-$rhs end]
lappend outputlines $a$b$c
} else {
#overlay wider or equal
set rendered [renderline -transparent $opt_transparent -overflow [dict get $opts -overflow] $undertext $overtext]
if {$diff < 0} {
#overlay is wider - trim if overflow not specified in opts
if {![dict get $opts -overflow]} {
#lappend outputlines [string range $overtext 0 [expr {$colwidth - 1}]]
#set overtext [string range $overtext 0 $colwidth-1 ]
if {[dict get $opts -ellipsis]} {
set rendered [overtype::right $rendered $opt_ellipsistext]
}
}
} else {
#widths match
}
lappend outputlines $rendered
#lappend outputlines [renderline -transparent $opt_transparent $undertext $overtext]
}
}
return [join $outputlines \n]
}
# -- --- --- --- --- --- --- --- --- --- ---
#todo - ansi
proc overtype::transparentline {args} {
foreach {under over} [lrange $args end-1 end] break
set argsflags [lrange $args 0 end-2]
set defaults [dict create\
-transparent 1\
]
set newargs [dict merge $defaults $argsflags]
tailcall overtype::renderline {*}$newargs $under $over
}
#renderline may not make sense as it is in the long run. We are trying to handle ansi codes in a block of text which is acting like a mini-terminal in some sense.
#We can process standard cursor moves such as \b \r - but no way to respond to other cursor movements e.g moving to other lines.
#
proc overtype::renderline {args} {
if {[llength $args] < 2} {
error {usage: ?-transparent [0|1|<regexp>]? ?-overflow [1|0]? undertext overtext}
}
foreach {under over} [lrange $args end-1 end] break
if {[string first \n $under] >=0 || [string first \n $over] >= 0} {
error "overtype::renderline not allowed to contain newlines"
}
set defaults [dict create\
-overflow 0\
-transparent 0\
]
set known_opts [dict keys $defaults]
set argsflags [lrange $args 0 end-2]
dict for {k v} $argsflags {
if {$k ni $known_opts} {
error "overtype::renderline unknown option '$k'. Known options: $known_opts"
}
}
set opts [dict merge $defaults $argsflags]
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_overflow [dict get $opts -overflow]
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_transparent [dict get $opts -transparent]
if {$opt_transparent eq "0"} {
set do_transparency 0
} else {
set do_transparency 1
if {$opt_transparent eq "1"} {
set opt_transparent {[\s]}
}
}
# -- --- --- --- --- --- --- --- --- --- --- ---
if {[string first \t $under] >= 0} {
set under [textutil::tabify::untabify2 $under]
}
if {[string first \t $over] >= 0} {
set over [textutil::tabify::untabify2 $over]
}
set bs [format %c 0x08]
set escape_terminals [list J K m n A B C D E F G s u] ;#review - this definitely doesn't cover all
#todo - ansi split printables in the undertext - maintaining colorcodes etc
set out [split $under ""]
set under_printables [list]
set in_escapesequence 0
#assumption - undertext already 'rendered' - ie no backspaces or carriagereturns or other cursor movement controls
#this is basically a form of stripansi... review!
foreach u [split $under ""] {
if {$in_escapesequence} {
if {$u in $escape_terminals} {
set in_escapesequence 0
}
} else {
if {$u eq "\033"} {
set in_escapesequence 1
} else {
lappend under_printables $u
}
}
}
set original_under_printlen [printing_length $under]
set original_under_printlen2 [llength $under_printables]
if {$original_under_printlen != $original_under_printlen2} {
puts stderr "overtype::renderline WARNING - differing printable length calculations for under text: $under"
puts stderr "printing_length: $original_under_printlen"
puts stderr "llength under_printables: $original_under_printlen2"
}
#assert - same as llength $under_printables ?
set i 0
set in_escapesequence 0
#we still want to copy over escape sequences - but we need to keep track of printables
set i_printable 0
foreach o [split $over ""] {
if {$in_escapesequence} {
if {$o in $escape_terminals} {
set in_escapesequence 0
}
#pass through to output but not printables
priv::renderline_insertchar $i $o
incr i
} else {
if {$o eq "\033"} {
set in_escapesequence 1
priv::renderline_insertchar $i $o
incr i
} elseif {$o eq "\r"} {
set i 0
set i_printable 0
} elseif {$o eq $bs} {
if {$i > 0} {
#review - we potentially need to backspace beyond ansi code!
incr i -1
}
if {$i_printable > 0} {
incr i_printable -1
}
} elseif {$do_transparency && [regexp $opt_transparent $o]} {
if {$i > [llength $out]-1} {
lappend out " "
}
incr i
if {$i_printable > [llength $under_printables]-1} {
lappend under_printables " "
}
incr i_printable
} else {
priv::renderline_addchar $i $o
priv::renderline_addprintable $i_printable $o
if {!$opt_overflow} {
if {$i_printable == $original_under_printlen} {
#we have reached our cutoff length - but there could be a closing ansi code or other control characters that would change the already-processed output.
#also - trailing ansi escapes should remain to affect subsequent lines - we really just want to suppress the printables from this point on and possibly trim some corresponding codes.
#(e.g color codes that are opened and closed around trimmed printables)
#todo - something!!
break
}
}
incr i
incr i_printable
}
}
}
return [join $out ""]
}
namespace eval overtype::priv {
proc renderline_addchar {i c} {
upvar out o
set nxt [llength $o]
if {$i < $nxt} {
lset o $i $c
} else {
lappend o $c
}
}
proc renderline_insertchar {i c} {
upvar out o
set nxt [llength $o]
if {$i < $nxt} {
set o [linsert $o $i $c]
} else {
lappend o $c
}
}
proc renderline_addprintable {i c} {
upvar under_printables printables
set nxt [llength $printables]
if {$i < $nxt} {
lset printables $i $c
} else {
lappend printables $c
}
}
}
# -- --- --- --- --- --- --- --- --- --- ---
proc overtype::centre_prev {args} {
if {[llength $args] < 2} {
error {usage: ?-bias [left|right]? ?-overflow [1|0]? undertext overtext}
}
foreach {undertext overtext} [lrange $args end-1 end] break
set opt(-bias) left
set opt(-overflow) 0
array set opt [lrange $args 0 end-2]
set olen [printing_length $overtext]
set ulen [printing_length $undertext]
set diff [expr {$ulen - $olen}]
if {$diff > 0} {
set half [expr {round(int($diff / 2))}]
if {[string match right $opt(-bias)]} {
if {[expr {2 * $half}] < $diff} {
incr half
}
}
set rhs [expr {$diff - $half - 1}]
set lhs [expr {$half - 1}]
set a [string range $undertext 0 $lhs]
set b $overtext
set c [string range $undertext end-$rhs end]
return $a$b$c
} else {
if {$diff < 0} {
if {$opt(-overflow)} {
return $overtext
} else {
return [string range $overtext 0 [expr {$ulen - 1}]]
}
} else {
return $overtext
}
}
}
proc overtype::right {args} {
# @d !todo - implement overflow, length checks etc
if {[llength $args] < 2} {
error {usage: ?-overflow [1|0]? undertext overtext}
}
foreach {undertext overtext} [lrange $args end-1 end] break
set opt(-overflow) 0
array set opt [lrange $args 0 end-2]
set olen [printing_length $overtext]
set ulen [printing_length $undertext]
if {$opt(-overflow)} {
return [string range $undertext 0 end-$olen]$overtext
} else {
if {$olen > $ulen} {
set diff [expr {$olen - $ulen}]
return [string range $undertext 0 end-$olen][string range $overtext 0 end-$diff]
} else {
return [string range $undertext 0 end-$olen]$overtext
}
}
}
namespace eval overtype {
interp alias {} ::overtype::center {} ::overtype::centre
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide overtype [namespace eval overtype {
variable version
set version 1.5.0
}]
return
Loading…
Cancel
Save