diff --git a/src/bootsupport/modules/punk/ansi-0.1.1.tm b/src/bootsupport/modules/punk/ansi-0.1.1.tm index 3d9988b1..3ec3ad9c 100644 --- a/src/bootsupport/modules/punk/ansi-0.1.1.tm +++ b/src/bootsupport/modules/punk/ansi-0.1.1.tm @@ -4072,7 +4072,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu # \033 - octal. equivalently \x1b in hex which is more common in documentation # empty list [a] should do reset - same for [a nonexistant] # explicit reset at beginning of parameter list for a= (as opposed to a+) - set t [linsert $t[unset t] 0 0] + #set t [linsert $t[unset t] 0 0] + ledit t -1 -1 0 if {![llength $e]} { set result "\x1b\[[join $t {;}]m" } else { diff --git a/src/bootsupport/modules/punk/lib-0.1.5.tm b/src/bootsupport/modules/punk/lib-0.1.5.tm index 390b34ae..c5b6ddd3 100644 --- a/src/bootsupport/modules/punk/lib-0.1.5.tm +++ b/src/bootsupport/modules/punk/lib-0.1.5.tm @@ -765,7 +765,7 @@ namespace eval punk::lib { struct::list swap doesn't support 'end' offsets, and only sometimes appears to support basic expressions, depending on the expression compared to the list length." - @values -min 1 -max 1 + @values -min 3 -max 3 lvar -type string -help\ "name of list variable" a -type indexexpression @@ -995,11 +995,8 @@ namespace eval punk::lib { e.g lzip {a b c d e} {1 2 3 4} {x y z} -> {a 1 x} {b 2 y} {c 3 z} {d 4 {}} {3 {} {}} " - @values -min 1 -max 1 - lvar -type string -help\ - "name of list variable" - a -type indexexpression - z -type indexexpression + @values -min 0 -max -1 + list -type list -multiple 1 -optional 1 }] } proc lzip {args} { diff --git a/src/bootsupport/modules/punk/path-0.1.0.tm b/src/bootsupport/modules/punk/path-0.1.0.tm index 157e8f30..997ea3c3 100644 --- a/src/bootsupport/modules/punk/path-0.1.0.tm +++ b/src/bootsupport/modules/punk/path-0.1.0.tm @@ -294,7 +294,8 @@ namespace eval punk::path { } } elseif {[lindex $parts 0] ne ""} { #relpath a/b/c - set parts [linsert $parts 0 .] + #set parts [linsert $parts 0 .] + ledit parts -1 -1 . set rootindex 0 #allow backtracking arbitrarily for leading .. entries - simplify where possible #also need to stop possible conversion to absolute path @@ -1091,7 +1092,8 @@ namespace eval punk::path { # loc is: ref/sub = sub while {$reference_len > 0} { - set location [linsert $location 0 ..] + #set location [linsert $location 0 ..] + ledit location -1 -1 .. incr reference_len -1 } set location [file join {*}$location] diff --git a/src/bootsupport/modules/textblock-0.1.3.tm b/src/bootsupport/modules/textblock-0.1.3.tm index 4079254e..abef420d 100644 --- a/src/bootsupport/modules/textblock-0.1.3.tm +++ b/src/bootsupport/modules/textblock-0.1.3.tm @@ -5400,7 +5400,8 @@ tcl::namespace::eval textblock { l-2 { if {$lnum == 0} { if {[lindex $line_chunks 0] eq ""} { - set line_chunks [linsert $line_chunks 2 $pad] + #set line_chunks [linsert $line_chunks 2 $pad] + ledit line_chunks 2 1 $pad } else { #set line_chunks [linsert $line_chunks 0 $pad] ledit line_chunks -1 -1 $pad diff --git a/src/modules/overtype-999999.0a1.0.tm b/src/modules/overtype-999999.0a1.0.tm index 1555a5fa..7f178212 100644 --- a/src/modules/overtype-999999.0a1.0.tm +++ b/src/modules/overtype-999999.0a1.0.tm @@ -258,6 +258,7 @@ tcl::namespace::eval overtype { -wrap -default 0 -type boolean -info -default 0 -type boolean -help\ "When set to 1, return a dictionary (experimental)" + -binarytext -default "" -type string -choices {"" bios ice} -console -default {stdin stdout stderr} -type list @values -min 1 -max 2 @@ -329,6 +330,7 @@ tcl::namespace::eval overtype { -insert_mode 0\ -wrap 0\ -info 0\ + -binarytext ""\ -console {stdin stdout stderr}\ ] #expand_right is perhaps consistent with the idea of the page_size being allowed to grow horizontally.. @@ -349,7 +351,7 @@ tcl::namespace::eval overtype { - -expand_right - -appendlines - -reverse_mode - -crm_mode - -insert_mode - -cp437 - - -info - -console { + - -info - -binarytext - -console { tcl::dict::set opts $k $v } -wrap - -autowrap_mode { @@ -389,6 +391,7 @@ tcl::namespace::eval overtype { # -- --- --- --- --- --- set opt_cp437 [tcl::dict::get $opts -cp437] set opt_info [tcl::dict::get $opts -info] + set opt_binarytext [tcl::dict::get $opts -binarytext] @@ -534,11 +537,44 @@ tcl::namespace::eval overtype { } 4 { set inputchunks [list] - foreach ln [split $overblock \n] { - lappend inputchunks [list mixed $ln\n] - } - if {[llength $inputchunks]} { - lset inputchunks end 1 [tcl::string::range [lindex $inputchunks end 1] 0 end-1] + switch -- $opt_binarytext { + "" { + foreach ln [split $overblock \n] { + lappend inputchunks [list mixed $ln\n] + } + if {[llength $inputchunks]} { + lset inputchunks end 1 [tcl::string::range [lindex $inputchunks end 1] 0 end-1] + } + } + bios { + #16 fg, 8 fg + possible blink + set input "" + set ansisplit [list ""] + set charpair 0 + foreach {ch at} [split $overblock ""] { + #review - does binarytext only apply to cp437??? we need to know the original encoding + set at [encoding convertto cp437 $at] + if {[catch {punk::ansi::colour::byteAnsi $at} code]} { + puts stderr "renderspace err at charpair: $charpair [punk::ansi::ansistring VIEW ${ch}${at}]" + #append input [punk::ansi::a+ brightred White] \uFFef + set code [punk::ansi::a+ brightred White] + set ch \uFFeF + } + append input $code $ch + lappend ansisplit $code $ch + incr charpair + } + #lappend inputchunks [list mixed $input] + lappend inputchunks [list ansisplit $ansisplit] + } + ice { + #16 fg, 16 bg (no blink) + set input "" + foreach {ch at} [split $overblock ""] { + append input [punk::ansi::colour::byteAnsiIce $at]$ch + } + lappend inputchunks [list mixed $input] + } } } } @@ -2299,8 +2335,12 @@ tcl::namespace::eval overtype { set i_u -1 ;#underlay may legitimately be empty set undercols [list] set u_codestack [list] + #------------- #u_gx_stack probably isn't really a stack - I don't know if g0 g1 can stack or not - for now we support only g0 anyway set u_gx_stack [list] ;#separate stack for g0 (g1 g2 g3?) graphics on and off (DEC special graphics) + # + #------------- + #set pt_underchars "" ;#for string_columns length calculation for expand_right 0 truncation set remainder [list] ;#for returnextra foreach {pt code} $undermap { @@ -2325,6 +2365,7 @@ tcl::namespace::eval overtype { #review - 0 and 1? incr i_u $ptlen lappend understacks {*}[lrepeat $ptlen $u_codestack] + #we need to store the gx0 state per column - as characters with or without gx0 can be overlayed anywhere lappend understacks_gx {*}[lrepeat $ptlen $u_gx_stack] lappend undercols {*}[lrepeat $ptlen $p1] } else { @@ -2505,24 +2546,6 @@ tcl::namespace::eval overtype { } - if 0 { - # ----------------- - # if we aren't extending understacks & understacks_gx each time we incr idx above the undercols length.. this doesn't really serve a purpose - # Review. - # ----------------- - #replay code for last overlay position in input line - # whether or not we get that far - we need to return it for possible replay on next line - if {[llength $understacks]} { - lappend understacks $u_codestack - lappend understacks_gx $u_gx_stack - } else { - #in case overlay onto emptystring as underlay - lappend understacks [list] - lappend understacks_gx [list] - } - # ----------------- - } - #trailing codes in effect for underlay if {[llength $u_codestack]} { #set replay_codes_underlay [join $u_codestack ""] @@ -2750,7 +2773,7 @@ tcl::namespace::eval overtype { set o_codestack [list $temp_cursor_saved] lappend overlay_grapheme_control_list [list other $code] } else { - #review + #review - gx0 should just be a flag like autowrap_mode and insert_mode? if {[punk::ansi::codetype::is_gx_open $code]} { set o_gxstack [list "gx0_on"] lappend overlay_grapheme_control_list [list gx0 gx0_on] ;#don't store code - will complicate debugging if we spit it out and jump character sets diff --git a/src/modules/punk-0.1.tm b/src/modules/punk-0.1.tm index 1893444d..b7a6da58 100644 --- a/src/modules/punk-0.1.tm +++ b/src/modules/punk-0.1.tm @@ -8049,21 +8049,9 @@ namespace eval punk { # punk::nav::fs package require punk::nav::fs - interp alias {} ./ {} punk::nav::fs::d/ - interp alias {} ../ {} punk::nav::fs::dd/ - interp alias {} d/ {} punk::nav::fs::d/ - interp alias {} dd/ {} punk::nav::fs::dd/ - - interp alias {} vwd {} punk::nav::fs::vwd ;#return punk::nav::fs::VIRTUAL_CWD - and report to stderr pwd if different - interp alias {} dirlist {} punk::nav::fs::dirlist - interp alias {} dirfiles {} punk::nav::fs::dirfiles - interp alias {} dirfiles_dict {} punk::nav::fs::dirfiles_dict - - interp alias {} ./new {} punk::nav::fs::d/new - interp alias {} d/new {} punk::nav::fs::d/new - interp alias {} ./~ {} punk::nav::fs::d/~ - interp alias {} d/~ {} punk::nav::fs::d/~ - interp alias "" x/ "" punk::nav::fs::x/ + package require punk::nav::ns + + variable pshell_path "" # ---------------------------------------- diff --git a/src/modules/punk/ansi-999999.0a1.0.tm b/src/modules/punk/ansi-999999.0a1.0.tm index dc508a3c..4ef03f62 100644 --- a/src/modules/punk/ansi-999999.0a1.0.tm +++ b/src/modules/punk/ansi-999999.0a1.0.tm @@ -119,17 +119,39 @@ tcl::namespace::eval punk::ansi::class { #todo - store rendered and allow partial rendering of new data lines? return $o_rendered } - method rendertest {{dimensions ""}} { - if {$dimensions eq ""} { - set dimensions $o_render_dimensions - } - if {![regexp {^([0-9]+)[xX]([0-9]+)$} $dimensions _m w h]} { + lappend ::punk::ansi::class::PUNKARGS [list { + @id -id "::punk::ansi::class::class_ansi rendertest" + @cmd -name "punk::ansi::class::class_ansi rendertest" -help\ + "" + @opts + -width -type integer -default "" + -height -type integer -default "" + -crm_mode -type boolean -default 0 + -binarytext -type string -default "" -choices {"" bios ice} + @values -min 0 -max 0 + }] + method rendertest {args} { + set argd [punk::args::parse $args withid "::punk::ansi::class::class_ansi rendertest"] + set opt_width [dict get $argd opts -width] + set opt_height [dict get $argd opts -height] + set opt_crm_mode [dict get $argd opts -crm_mode] + set opt_binarytext [dict get $argd opts -binarytext] + + set existing_dimensions $o_render_dimensions + if {![regexp {^([0-9]+)[xX]([0-9]+)$} $existing_dimensions _m w h]} { error "class_ansi::render dimensions must be of the form x" } - set o_dimensions $dimensions + if {$opt_width ne ""} { + set w $opt_width + } + if {$opt_height ne ""} { + set h $opt_height + } + set o_render_dimensions ${w}x${h} - set rendered [overtype::renderspace -cp437 1 -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] + + set rendered [overtype::renderspace -binarytext $opt_binarytext -cp437 1 -crm_mode $opt_crm_mode -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] return $rendered } @@ -414,6 +436,9 @@ tcl::namespace::eval punk::ansi { tcl::dict::set cp437_map \u001E \u25B2 ;#up arrow triangle tcl::dict::set cp437_map \u001F \u25BC ;#down arrow triangle + #del control character + tcl::dict::set cp437_map \u007F \u2302 ;#House + variable map_special_graphics #DEC Special Graphics set https://en.wikipedia.org/wiki/DEC_Special_Graphics #AKA IBM Code page 1090 @@ -561,7 +586,175 @@ tcl::namespace::eval punk::ansi { } return $obj } - proc ansicat {fname args} { + + namespace eval argdoc { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id ::punk::ansi::sauce + @cmd -name punk::ansi::sauce -summary\ + "SAUCE info from file"\ + -help\ + "Wrapper for punk::ansi::sauce::from_file to display SAUCE block data." + -encoding -default iso8859-1 -type string -help\ + "The default iso8859-1 is equivalent to binary ans should + work in the usual case. + If the entire file including the trailing SAUCE block was encoded + in another manner, -encoding may be supplied to decode prior to + examining SAUCE data. + (unimplemented)" + -return -type string -default tables -choices {dict string} + @values -min 1 -max 1 + filename -type string + }] + } + proc sauce {args} { + set argd [punk::args::parse $args withid ::punk::ansi::sauce] + lassign [dict values $argd] leaders opts values + set filename [dict get $values filename] + set opt_return [dict get $opts -return] + + package require punk::ansi::sauce + set sdict [punk::ansi::sauce::from_file $filename] + set result "" + if {[dict size $sdict]} { + if {$opt_return eq "dict"} { + return $sdict + } + if {[dict exists $sdict commentlines]} { + set clines [dict get $sdict commentlines] + set cblock [join $clines \n] + append result [textblock::frame -title "SAUCE comments" $cblock] + } + dict unset sdict commentlines + append result \n [punk::lib::showdict $sdict] + } + return $result + } + + namespace eval argdoc { + variable PUNKARGS + lappend PUNKARGS [list { + @id -id ::punk::ansi::ansicat + @cmd -name punk::ansi::ansicat -help\ + "Display ANSI image - using SAUCE data if any to determine width and encoding." + -encoding -type string -help\ + "Force a particular encoding eg utf-8 or cp437. + Default is cp437 - or as specified in any SAUCE data." + -dimensions -type string -help\ + "Force specific COLxROW dimensions + e.g 82x30 + + Default is 80x24 when no SAUCE data exists. + If SAUCE data specifies dimensions, they will + be used if this value is not supplied. + " + -crm_mode -type binary -default 0 + -sauce -type none -help\ + "Display SAUCE and comments after image" + @values -min 1 -max 1 + filename -type string + }] + } + proc ansicat {args} { + set argd [punk::args::parse $args withid ::punk::ansi::ansicat] + lassign [dict values $argd] leaders opts values received + set fname [dict get $values filename] + if {[dict exists $received -encoding]} { + set encoding [dict get $opts -encoding] + set encnames [encoding names] + if {$encoding ni $encnames} { + error "punk::ansi::ansicat unknown encoding $encoding should be one of the values from 'encoding names'" + } + } else { + set encoding "" + } + if {[dict exists $received -dimensions]} { + set dimensions [dict get $opts -dimensions] + if {![regexp {[0-9]+(?:x|X)[0-9]+} $dimensions]} { + error "punk::ansi::ansicat bad dimension specification $a should be of form WxH" + } + } else { + set dimensions "" + } + set opt_crm_mode [dict get $opts -crm_mode] + + #if SAUCE data is present - it may give an indication of encoding as well as number of columns/lines + package require punk::ansi::sauce + set binarytext "" + if {[catch {punk::ansi::sauce::from_file $fname} sdict]} { + #no 128 Byte SAUCE record at end of file + set sdict [dict create] + } + if {![dict size $sdict]} { + if {[string tolower [file extension $fname]] eq ".bin"} { + #In the absence of SAUCE data - assume .bin is binary text + set binarytext bios ;#16 fg, 8 bg + blink + } + } + if {[dict exists $sdict datatype_name]} { + if {[dict get $sdict datatype_name] eq "binarytext"} { + #todo - SAUCE ANSiFlags - ice vs default bios + set binarytext bios + } + } + if {$encoding eq ""} { + if {[dict exists $sdict codepage]} { + set encoding [dict get $sdict codepage] + } else { + #default + set encoding cp437 + } + } + + if {$dimensions eq ""} { + # defaults + set cols 80 + set rows 24 + if {[dict exists $sdict columns]} { + set c [dict get $sdict columns] + if {$c > 0} { + set cols $c + } + } + if {[dict exists $sdict rows]} { + set r [dict get $sdict rows] + if {$r > 0} { + set rows $r + } + } + set dimensions ${cols}x${rows} + } + lassign [split $dimensions x] cols rows + + #set ansidata [fcat -encoding $encoding $fname] + set filedata [fcat -translation binary $fname] + set parts [split $filedata \x1a] + set ansidata [lindex $parts 0] + + #hack + #if {$binarytext eq ""} { + set ansidata [encoding convertfrom $encoding $ansidata] + #} + + set obj [punk::ansi::class::class_ansi new $ansidata] + if {$encoding eq "cp437"} { + set result [$obj rendertest -binarytext $binarytext -width $cols -height $rows -crm_mode $opt_crm_mode] + } else { + set result [$obj render $dimensions] + } + $obj destroy + if {[dict exists $received -sauce] && [dict size $sdict]} { + if {[dict exists $sdict commentlines]} { + set clines [dict get $sdict commentlines] + dict unset sdict commentlines + set cblock [join $clines \n] + append result \n [textblock::frame -title "SAUCE comments" $cblock] + } + append result \n [punk::lib::showdict $sdict] + } + return $result + } + proc ansicat1 {fname args} { set encnames [encoding names] set encoding "" set dimensions "" @@ -574,6 +767,9 @@ tcl::namespace::eval punk::ansi { } } } + #if SAUCE data is present - it may give an indication of encoding as well as number of columns/lines + package require punk::ansi::sauce + if {$encoding eq ""} { set encoding cp437 } @@ -581,11 +777,12 @@ tcl::namespace::eval punk::ansi { if {$dimensions eq ""} { set dimensions 80x24 } + lassign [split $dimensions x] cols rows set ansidata [fcat -encoding $encoding $fname] set obj [punk::ansi::class::class_ansi new $ansidata] if {$encoding eq "cp437"} { - set result [$obj rendertest $dimensions] + set result [$obj rendertest -width $cols -height $rows] } else { set result [$obj render $dimensions] } @@ -8841,7 +9038,19 @@ namespace eval punk::ansi::colour { #https://sourceforge.net/p/irrational-numbers/code/HEAD/tree/pkgs/Colors/trunk/colors.tcl#l159 - # classic formula for luminance (0.0 .. 100.0) + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id "::punk::ansi::colour::luminance" + @cmd -name "punk::ansi::colour::luminance" -help\ + "Classic formula for luminance (0.0 .. 1.0) + 8bit colour is assumed." + @opts + @values -min 3 -max 3 + R -type int -range {0 255} + G -type int -range {0 255} + B -type int -range {0 255} + }] + } proc luminance {R G B} { return [expr {(0.3*$R + 0.59*$G + 0.11*$B)/255.0}] } @@ -9017,6 +9226,195 @@ namespace eval punk::ansi::colour { return [list $hue $sat $lum] } + + variable byte_to_ansi + variable byte_to_ansi_ice + for {set i 0} {$i <= 255} {incr i} { + + #foreground + if {$i & 4 && $i & 2 && $i & 1} { + # 111 white + if {$i & 8} { + set fg rgb-255-255-255 + } else { + #low intensity (Light Gray) + set fg rgb-170-170-170 ;#or 192-192-192 + } + } elseif {$i & 4 && $i & 2} { + # 110 yellow + if {$i & 8} { + set fg rgb-255-255-0 + } else { + set fg rgb-170-85-0 ;#or 128-128-0 + } + } elseif {$i & 4 && $i & 1} { + # 101 magenta + if {$i & 8} { + set fg rgb-255-0-255 + } else { + set fg rgb-170-0-170 ;#or 128-0-128 + } + } elseif {$i & 4} { + # 100 red + if {$i & 8} { + set fg rgb-255-0-0 + } else { + set fg rgb-170-0-0 ;#or 128-0-0 + } + } elseif {$i & 2 && $i & 1} { + # 011 cyan + if {$i & 8} { + set fg rgb-0-255-255 + } else { + set fg rgb-0-170-170 ;#or 0-128-128 + } + } elseif {$i & 2} { + # 010 green + if {$i & 8} { + set fg rgb-0-255-0 + } else { + set fg rgb-0-170-0 ;#or 0-128-0 + } + } elseif {$i & 1} { + # 001 blue + if {$i & 8} { + set fg rgb-0-0-255 + } else { + set fg rgb-0-0-170 ;#or 0-0-128 + } + } else { + # 000 black + if {$i & 8} { + #high intensity (Dark Gray) + set fg rgb-85-85-85 + } else { + set fg rgb-0-0-0 + } + } + + #non-iCE background colours are the 8 low-intensity colours + set blink noblink + set bg "Rgb-0-0-0" + set ibg Rgb-0-0-0 + if {$i > 15} { + if {$i & 64 && $i & 32 && $i & 16} { + # 111 white + if {$i & 128} { + set ibg Rgb-255-255-255 + set blink blink + } else { + #low intensity (Light Gray) + set ibg Rgb-170-170-170 ;#or 192-192-192 + } + set bg Rgb-170-170-170 + + } elseif {$i & 64 && $i & 32} { + # 110 yellow + if {$i & 128} { + set ibg Rgb-255-255-0 + set blink blink + } else { + set ibg Rgb-170-85-0 ;#or 128-128-0 + } + set bg Rgb-170-85-0 + } elseif {$i & 64 && $i & 16} { + # 101 magenta + if {$i & 128} { + set ibg Rgb-255-0-255 + set blink blink + } else { + set ibg Rgb-170-0-170 ;#or 128-0-128 + } + set bg Rgb-170-0-170 + } elseif {$i & 64} { + # 100 red + if {$i & 128} { + set ibg Rgb-255-0-0 + set blink blink + } else { + set ibg Rgb-170-0-0 ;#or 128-0-0 + } + set bg Rgb-170-0-0 + } elseif {$i & 32 && $i & 16} { + # 011 cyan + if {$i & 128} { + set ibg Rgb-0-255-255 + set blink blink + } else { + set ibg Rgb-0-170-170 ;#or 0-128-128 + } + set bg Rgb-0-170-170 + } elseif {$i & 32} { + # 010 green + if {$i & 128} { + set ibg Rgb-0-255-0 + set blink blink + } else { + set ibg Rgb-0-170-0 ;#or 0-128-0 + } + set bg Rgb-0-170-0 + } elseif {$i & 16} { + # 001 blue + if {$i & 128} { + set ibg Rgb-0-0-255 + set blink blink + } else { + set ibg Rgb-0-0-170 ;#or 0-0-128 + } + set bg Rgb-0-0-170 + } else { + # 000 black + #high intensity (Dark Gray) + set ibg Rgb-85-85-85 + } + } + dict set byte_to_ansi [format %c $i] [punk::ansi::a+ {*}$blink $fg $bg] + dict set byte_to_ansi_ice [format %c $i] [punk::ansi::a+ $fg $ibg] + } + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id "::punk::ansi::colour::byteAnsi" + @cmd -name "punk::ansi::colour::byteAnsi" -summary\ + "ANSI/BIOS colour codes from attribute byte."\ + -help\ + "Convert an attribute-byte (character) to ANSI SGR + foreground and background colour. + This is allows 16 foreground colours and only 8 + background colours, with the highest bit being + used to set 'blink' on. + + Use the byteAnsiIce for 16 background colours." + @opts + @values -min 1 -max 1 + char -type char + }] + } + proc byteAnsi {char} { + variable byte_to_ansi + dict get $byte_to_ansi $char + } + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id "::punk::ansi::colour::byteAnsiIce" + @cmd -name "punk::ansi::colour::byteAnsiIce" -summary\ + "iCE colour codes from attribute byte."\ + -help\ + "Convert an attribute-byte (character) to ANSI SGR + foreground and background colour. + This is allows 16 foreground colours and 16 + background colours. (no blink capability) + + Use the byteAnsi for ANSI/BIOS 16 foreground, + 8 background colours with blink capability." + @opts + @values -min 1 -max 1 + char -type char + }] + } + proc byteAnsiIce {char} { + variable byte_to_ansi_ice + dict get $byte_to_ansi_ice $char + } } tcl::namespace::eval punk::ansi::internal { proc splitn {str {len 1}} { @@ -9115,7 +9513,7 @@ interp alias {} ansistring {} ::punk::ansi::ansistring namespace eval ::punk::args::register { #use fully qualified so 8.6 doesn't find existing var in global namespace - lappend ::punk::args::register::NAMESPACES ::punk::ansi ::punk::ansi::argdoc ::punk::ansi::class ::punk::ansi::ta + lappend ::punk::args::register::NAMESPACES ::punk::ansi ::punk::ansi::colour ::punk::ansi::argdoc ::punk::ansi::class ::punk::ansi::ta } # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ diff --git a/src/modules/punk/ansi/sauce-999999.0a1.0.tm b/src/modules/punk/ansi/sauce-999999.0a1.0.tm new file mode 100644 index 00000000..bdee79d4 --- /dev/null +++ b/src/modules/punk/ansi/sauce-999999.0a1.0.tm @@ -0,0 +1,573 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -buildversion.txt +# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.4.tm +# +# 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) 2025 +# +# @@ Meta Begin +# Application punk::ansi::sauce 999999.0a1.0 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +package require Tcl 8.6- + + + +tcl::namespace::eval punk::ansi::sauce { + variable PUNKARGS + namespace eval argdoc { + variable PUNKARGS + #non-colour SGR codes + set I "\x1b\[3m" ;# [a+ italic] + set NI "\x1b\[23m" ;# [a+ noitalic] + set B "\x1b\[1m" ;# [a+ bold] + set N "\x1b\[22m" ;# [a+ normal] + set T "\x1b\[1\;4m" ;# [a+ bold underline] + set NT "\x1b\[22\;24m\x1b\[4:0m" ;# [a+ normal nounderline] + } + + proc from_file {fname} { + if {[file size $fname] < 128} { + return + } + set fd [open $fname r] + chan conf $fd -translation binary + chan seek $fd -128 end + set srec [read $fd] + set srec_len 128 ;#This is the normal length of a SAUCE record - we may need to set it shorter if truncation detected + if {[catch {set sdict [to_dict $srec]}]} { + #review - have seen truncated SAUCE records < 128 bytes + #we could search for SAUCE00 in the tail and see what records can be parsed? + #specifically publicdomain roysac images sometimes only 99 Bytes of sauce - suspect remaining were null \x0 padded and trimmed + set sauceposn [string first SAUCE00 $srec] + if {$sauceposn <= 0} { + close $fd + return + } + #emit something to give user an indication something isn't right + puts stderr "punk::ansi::sauce::from_file WARNING SAUCE record seems to be truncated - padding rhs with nulls and trying again.." + #SAUCE00 is not at the beginning + #pad the tail with nulls and try again + set srec [string range $srec $sauceposn end] + set srec_len [string length $srec] + set srec ${srec}[string repeat \x0 [expr {128 - [string length $srec]}]] + if {[catch {set sdict [to_dict $srec]}]} { + close $fd + return + } + dict set sdict warning "SAUCE truncation to $srec_len bytes detected" + } + if {[dict exists $sdict comments] && [dict get $sdict comments] > 0} { + set clines [dict get $sdict comments] + #Use srec_len instead of 128 - in case we had truncated source record which we padded and were able to parse + set offset [expr {-1 *($srec_len + ($clines * 64) + 5)}] + chan seek $fd $offset end + set tag [chan read $fd 5] + if {$tag eq "COMNT"} { + #'character' data - shouldn't be null terminated c-style string - but can be + set commentlines [list] + for {set c 0} {$c < $clines} {incr c} { + set rawline [chan read $fd 64] + if {![catch {binary scan $rawline C* str} errM]} { + set ln [format %-64s $str] + } else { + set ln [string repeat " " 64] + } + if {![catch {encoding convertfrom cp437 $ln} line]} { + lappend commentlines $line + } else { + catch { + package require punk::ansi + puts stderr "punk::ansi::sauce::from_file failed to decode (from cp437) comment line:[punk::ansi::ansistring VIEW $ln]" + } + lappend commentlines [string repeat " " 64] + } + } + dict set sdict commentlines $commentlines + } + } + close $fd + return $sdict + } + + set datatypes [dict create] + dict set datatypes 0 none + dict set datatypes 1 character + dict set datatypes 2 bitmap + dict set datatypes 3 vector + dict set datatypes 4 audio + dict set datatypes 5 binarytext + dict set datatypes 6 xbin + dict set datatypes 7 archive + dict set datatypes 8 executable + + set filetypes [dict create] + + #Character + dict set filetypes 1 0 [list name "ASCII" description "Plain ASCII text file with no formatting codes or color codes."] + dict set filetypes 1 1 [list name "ANSi" description "A file with ANSi coloring codes and cursor positioning."] + dict set filetypes 1 2 [list name "ANSiMation" description "Like an ANSi file, but it relies on a fixed screensize."] + dict set filetypes 1 3 [list name "RIP script" description "Remote Imaging Protocol Graphics."] + dict set filetypes 1 4 [list name "PCBoard" description "A file with PCBoard color codes and macros, and ANSi codes."] + dict set filetypes 1 5 [list name "Avatar" description "A file with Avatar color codes, and ANSi codes."] + dict set filetypes 1 6 [list name "HTML" description "HyperText Markup Language."] + dict set filetypes 1 7 [list name "Source" description "Source code for some programming language.\nThe file extension should determine the programming language."] + dict set filetypes 1 8 [list name "TundraDraw" description "A TundraDraw file.\nLike ANSi, but with a custom palette."] + + #Bitmap + dict set filetypes 2 0 [list name "GIF" description "CompuServe Graphics Interchange Format"] + dict set filetypes 2 1 [list name "PCX" description "ZSoft Paintbrush PCX"] + dict set filetypes 2 2 [list name "LBM/IFF" description "DeluxePaint LBM/IFF"] + dict set filetypes 2 3 [list name "TGA" description "Targa Truecolor"] + dict set filetypes 2 4 [list name "FLI" description "Autodesk FLI animation"] + dict set filetypes 2 5 [list name "FLC" description "Autodesk FLC animation"] + dict set filetypes 2 6 [list name "BMP" description "Windows or OS/2 Bitmap"] + dict set filetypes 2 7 [list name "GL" description "Grasp GL Animation"] + dict set filetypes 2 8 [list name "DL" description "DL Animation"] + dict set filetypes 2 9 [list name "WPG" description "Wordperfect Bitmap"] + dict set filetypes 2 10 [list name "PNG" description "Portable Network Graphics"] + dict set filetypes 2 11 [list name "JPG/JPeg" description "JPeg image (any subformat)"] + dict set filetypes 2 12 [list name "MPG" description "MPeg video (any subformat)"] + dict set filetypes 2 12 [list name "AVI" description "Audio Video Interleave (any subformat)"] + + #vector + dict set filetypes 3 0 [list name "DXF" description "CAD Drawing eXchange Format"] + dict set filetypes 3 1 [list name "DWG" description "AutoCAD Drawing File"] + dict set filetypes 3 2 [list name "WPG" description "WordPerfect or DrawPerfect vector graphics"] + dict set filetypes 3 3 [list name "3DS" description "3D Studio"] + + #Audio + dict set filetypes 4 0 [list name "MOD" description "4, 6 or 8 channel MOD (Noise Tracker)"] + dict set filetypes 4 1 [list name "669" description "Renaissance 8 channel 669"] + dict set filetypes 4 2 [list name "STM" description "Future Crew 4 channel ScreamTracker"] + dict set filetypes 4 3 [list name "S3M" description "Future Crew variable channel ScreamTracker 3"] + dict set filetypes 4 4 [list name "MTM" description "Renaissance variable channel MultiTracker"] + dict set filetypes 4 5 [list name "FAR" description "Farandole composer"] + dict set filetypes 4 6 [list name "ULT" description "UltraTracker"] + dict set filetypes 4 7 [list name "AMF" description "DMP/DSMI Advanced Module Format"] + dict set filetypes 4 8 [list name "DMF" description "Delusion Digital Music Format (XTracker)"] + dict set filetypes 4 9 [list name "OKT" description "Oktalyser"] + dict set filetypes 4 10 [list name "ROL" description "AdLib ROL file (FM audio)"] + dict set filetypes 4 11 [list name "CMF" description "Creative Music File (FM Audio)"] + dict set filetypes 4 12 [list name "MID" description "MIDI (Musical Instrument Digital Interface)"] + dict set filetypes 4 13 [list name "SADT" description "SAdT composer (FM Audio)"] + dict set filetypes 4 14 [list name "VOC" description "Creative Voice File"] + dict set filetypes 4 15 [list name "WAV" description "Waveform Audio File Format"] + dict set filetypes 4 16 [list name "SMP8" description "Raw, single channel 8 bit sample"] + dict set filetypes 4 17 [list name "SMP8S" description "Raw, stereo 8 bit sample"] + dict set filetypes 4 18 [list name "SMP16" description "Raw, single channel 16 bit sample"] + dict set filetypes 4 19 [list name "SMP16S" description "Raw, stereo 16 bit sample"] + dict set filetypes 4 20 [list name "PATCH8" description "8 Bit patch file"] + dict set filetypes 4 21 [list name "PATCH16" description "16 Bit patch file"] + dict set filetypes 4 22 [list name "XM" description "FastTracker \]\[ module"] + dict set filetypes 4 23 [list name "HSC" description "HSC Tracker (FM Audio)"] + dict set filetypes 4 24 [list name "IT" description "Impulse Tracker"] + + #Archive + dict set filetypes 7 0 [list name "ZIP" description "PKWare Zip"] + dict set filetypes 7 1 [list name "ARJ" description "Archive Robert K. Jung"] + dict set filetypes 7 2 [list name "LZH" description "Haruyasu Yoshizaki (Yoshi)"] + dict set filetypes 7 3 [list name "ARC" description "S.E.A"] + dict set filetypes 7 4 [list name "TAR" description "Unix TAR"] + dict set filetypes 7 5 [list name "ZOO" description "ZOO"] + dict set filetypes 7 6 [list name "RAR" description "RAR"] + dict set filetypes 7 7 [list name "UC2" description "UC2"] + dict set filetypes 7 8 [list name "PAK" description "PAK"] + dict set filetypes 7 9 [list name "SQZ" description "SQZ"] + + + #review + #map sauce encodings to those that exist by default in Tcl 'encoding names' + set encodings [dict create] + dict set encodings 437 cp437 + dict set encodings 720 cp1256 ;#Arabic + dict set encodings 737 cp737 + dict set encodings 775 cp775 + dict set encodings 819 iso8859-1 ;#Latin-1 Supplemental - review + dict set encodings 850 cp850 + dict set encodings 852 cp852 + dict set encodings 855 cp855 + dict set encodings 857 cp857 + #dict set encodings 858 "" ;#??? + dict set encodings 860 cp860 ;#Porguguese + dict set encodings 861 cp861 ;#Icelandic + dict set encodings 862 cp862 ;#Hebrew + dict set encodings 863 cp863 ;#French Canada + dict set encodings 864 cp864 + dict set encodings 865 cp865 + dict set encodings 866 cp866 ;#Cyrillic + dict set encodings 869 cp869 + #dict set encodings 872 "" ;#Cyrillic - cp855? macCyrillic? + #dict set encodings KAM "" ;#cp867,cp895 ? + #dict set encodings MAZ "" ;#cp667 cp790 ? + dict set encodings MIK cp866 ;#Cyrillic + + + + + #todo - fontName - which can also specify e.g code page 437 + ## Font name [1] Font size Resolution [2] Aspect ratio [3] Vertical stretch [6] Description + ## Display [4] Pixel [5] + + set fontnames [dict create] + + ## IBM VGA 9×16 [7] 720×400 4:3 20:27 (1:1.35) 35% Standard hardware font on VGA cards for 80×25 text mode (code page 437) + dict set fontnames "IBM VGA" [list fontsize "9x16" resolution "720x400" aspect_ratio_display "4:3" aspect_ratio_pixel "20:27 (1:1.35)" vertical_stretch "35%" description "Standard hardware font on VGA cards for 80×25 text mode (code page 437)"] + ## IBM VGA ### [8] 9×16 [7] 720×400 4:3 20:27 (1:1.35) 35% Software installed code page font for VGA 80×25 text mode + # - where ### is placeholder for 437,720,737 etc + + ## IBM VGA50 ### [8] 9×8 [7] 720×400 4:3 20:27 (1:1.35) 35% Software installed code page font for VGA condensed 80×50 text mode + ## IBM VGA25G ### [8] 8×19 640×480 4:3 1:1 0% Custom font for emulating 80×25 in VGA graphics mode 12 (640×480 16 color). + + ## 8×16 640×400 4:3 6:5 (1:1.2) 20% Modified stats when using an 8 pixel wide version of "IBM VGA" or code page variant. + ## IBM VGA50 9×8 [7] 720×400 4:3 20:27 (1:1.35) 35% Standard hardware font on VGA cards for condensed 80×50 text mode (code page 437) + ## 8×8 640×400 4:3 5:6 (1:1.2) 20% Modified stats when using an 8 pixel wide version of "IBM VGA50" or code page variant. + ## IBM VGA25G 8×19 640×480 4:3 1:1 0% Custom font for emulating 80×25 in VGA graphics mode 12 (640×480 16 color) (code page 437). + ## IBM EGA 8×14 640×350 4:3 35:48 (1:1.3714) 37.14% Standard hardware font on EGA cards for 80×25 text mode (code page 437) + ## IBM EGA43 8×8 640×350 4:3 35:48 (1:1.3714) 37.14% Standard hardware font on EGA cards for condensed 80×43 text mode (code page 437) + ## IBM EGA ### [8] 8×14 640×350 4:3 35:48 (1:1.3714) 37.14% Software installed code page font for EGA 80×25 text mode + ## IBM EGA43 ### [8] 8×8 640×350 4:3 35:48 (1:1.3714) 37.14% Software installed code page font for EGA condensed 80×43 text mode + ## Amiga Topaz 1 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Original Amiga Topaz Kickstart 1.x font. (A500, A1000, A2000) + ## Amiga Topaz 1+ 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Modified Amiga Topaz Kickstart 1.x font. (A500, A1000, A2000) + ## Amiga Topaz 2 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Original Amiga Topaz Kickstart 2.x font (A600, A1200, A4000) + ## Amiga Topaz 2+ 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Modified Amiga Topaz Kickstart 2.x font (A600, A1200, A4000) + ## Amiga P0T-NOoDLE 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Original P0T-NOoDLE font. + ## Amiga MicroKnight 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Original MicroKnight font. + ## Amiga MicroKnight+ 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Modified MicroKnight font. + ## Amiga mOsOul 8×8 [9] 640×200 4:3 5:12 (1:2.4) 140% Original mOsOul font. + ## C64 PETSCII unshifted 8×8 [10] 320×200 4:3 5:6 (1:1.2) 20% Original Commodore PETSCII font (PET, VIC-20, C64, CBM-II, Plus/4, C16, C116 and C128) in the unshifted mode. Unshifted mode (graphics) only has uppercase letters and additional graphic characters. This is the normal boot font. + ## C64 PETSCII shifted 8×8 [10] 320×200 4:3 5:6 (1:1.2) 20% Original PETSCII font in shifted mode. Shifted mode (text) has both uppercase and lowercase letters. This mode is actuated by pressing Shift+Commodore key. + ## Atari ATASCII 8×8 [11] 320×192 4:3 4:5 (1:1.25) 25% Original ATASCII font (Atari 400, 800, XL, XE) + + + #expect a 128 Byte sauce record + #Some sauce records may have been padded with null bytes - and been truncated by some process + + proc to_dict {saucerecord} { + variable datatypes + variable filetypes + variable encodings + if {[string length $saucerecord] != 128} { + error "punk::ansi::sauce::to_dict: Unable to interpret data as a SAUCE record - length != 128" + } + if {![string match "SAUCE*" $saucerecord]} { + error "punk::ansi::sauce::to_dict: Unable to interpret data as a SAUCE record - does not begin with 'SAUCE'" + } + #tcl binary scan: cu - unsigned 8-bit, su - unsigned 16-bit, iu - unsigned 32bit, + set sdict [dict create] + dict set sdict version [string range $saucerecord 5 6] ;#2bytes + + #sauce spec says 'character' type is a string encoded according to code page 437 (IBM PC / OEM ASCII) + # - in the wild - string may be terminated with null and have following garbage + # - 'binary scan $rawchars C* str' to get null-terminated string and pad rhs with spaces to cater for this possibility + + #dict set sdict title [string range $saucerecord 7 41] ;#35 bytes 'character' + set rawtitle [string range $saucerecord 7 41] ;#35 bytes 'character' + if {![catch {binary scan $rawtitle C* str} errM]} { + dict set sdict title [format %-35s $str] + } else { + dict set sdict title [string repeat " " 35] + } + + #dict set sdict author [string range $saucerecord 42 61] ;#20 bytes 'character' + set rawauthor [string range $saucerecord 42 61] ;#20 bytes 'character' + if {![catch {binary scan $rawauthor C* str} errM]} { + dict set sdict author [format %-20s $str] + } else { + dict set sdict author [string repeat " " 20] + } + + #dict set sdict group [string range $saucerecord 62 81] ;#20 bytes 'character' + set rawgroup [string range $saucerecord 62 81] ;#20 bytes 'character' + if {![catch {binary scan $rawgroup C* str} errM]} { + dict set sdict group [format %-20s $str] + } else { + dict set sdict group [string repeat " " 20] + } + + + + #dict set sdict date [string range $saucerecord 82 89] ;#8 bytes 'character' + set rawdata [string range $saucerecord 82 89] ;#8 bytes 'character' + if {![catch {binary scan $rawdate C* str} errM]} { + dict set sdict date [format %-8s $str] + } else { + dict set sdict date [string repeat " " 8] + } + + if {[binary scan [string range $saucerecord 90 93] iu v]} { + #4 bytes - unsigned littlendian + dict set sdict filesize $v + } else { + dict set sdict filesize "" + } + if {[binary scan [string range $saucerecord 94 94] cu v]} { + #1 byte - unsigned + dict set sdict datatype $v + if {[dict exists $datatypes [dict get $sdict datatype]]} { + dict set sdict datatype_name [dict get $datatypes [dict get $sdict datatype]] + } else { + dict set sdict datatype_name unrecognised + } + } else { + dict set sdict datatype "" + dict set sdict datatype_name failed ;#unrecognised?? + } + if {[binary scan [string range $saucerecord 95 95] cu v]} { + #1 byte - unsigned + dict set sdict filetype $v + if {[dict exists $filetypes [dict get $sdict datatype] $v]} { + dict set sdict filetype_name [dict get $filetypes [dict get $sdict datatype] $v name] + } else { + dict set sdict filetype_name "" + } + } else { + dict set sdict filetype "" + dict set sdict filetype_name "" + } + if {[binary scan [string range $saucerecord 96 97] su v]} { + dict set sdict tinfo1 $v + } else { + dict set sdict tinfo1 "" + } + + if {[binary scan [string range $saucerecord 98 99] su v]} { + dict set sdict tinfo2 $v + } else { + dict set sdict tinfo2 "" + } + + + if {[binary scan [string range $saucerecord 100 101] su v]} { + dict set sdict tinfo3 $v + } else { + dict set sdict tinfo3 "" + } + if {[binary scan [string range $saucerecord 102 103] su v]} { + dict set sdict tinfo4 $v + } else { + dict set sdict tinfo4 "" + } + if {[binary scan [string range $saucerecord 104 104] cu v]} { + #1 byte - unsigned + dict set sdict comments $v + } else { + dict set sdict comments 0 + } + if {[binary scan [string range $saucerecord 105 105] cu v]} { + dict set sdict tflags $v + } else { + dict set sdict tflags "" + } + set rawzstring [string range $saucerecord 106 127] + #Null terminated string use C to terminate at first null + if {[binary scan $rawzstring C* str]} { + dict set sdict tinfos $str + } else { + dict set sdict tinfos "" + } + + + + + switch -- [string tolower [dict get $sdict filetype_name]] { + ansi - ascii - pcboard - avatar { + dict set sdict columns [dict get $sdict tinfo1] + dict set sdict rows [dict get $sdict tinfo2] + dict set sdict fontname [dict get $sdict tinfos] + } + ansimation { + dict set sdict columns [dict get $sdict tinfo1] + #review - fixed screen height? + dict set sdict rows [dict get $sdict tinfo2] + dict set sdict fontname [dict get $sdict tinfos] + } + } + switch -- [dict get $sdict datatype] { + 5 { + #binarytext + #filetype represents half the characterwidth (only widths with multiples of 2 can be specified) + set cols [expr {2*[dict get $sdict filetype]}] + dict set sdict columns $cols + #rows must be calculated from file size + #rows = (filesize - sauceinfosize)/ filetype * 2 * 2 + #(time additional 2 due to character/attribute pairs) + + #todo - calc filesize from total size of file - EOF - comment - sauce rather than rely on stored filesize? + dict set sdict rows [expr {[dict get $sdict filesize]/($cols * 2)}] + + } + 6 { + #xbin - only filtype is 0 + #https://web.archive.org/web/20120204063040/http://www.acid.org/info/xbin/x_spec.htm + dict set sdict columns [dict get $sdict tinfo1] + dict set sdict rows [dict get $sdict tinfo2] + dict set sdict fontname [dict get $sdict tinfos] + } + } + if {[dict exists $sdict fontname]} { + set fname [dict get $sdict fontname] + #IBM VGA and IBM EGA variants are all cp437 - unless a 3 letter code specifying otherwise follows + switch -- [string range $fname 0 6] { + "IBM EGA" - "IBM VGA" { + lassign $fname _ibm _ code + set cp "" + if {$code eq ""} { + set cp "cp437" + } else { + if {[dict exists $encodings $code]} { + set cp [dict get $encodings $code] + } + } + if {$cp ne ""} { + dict set sdict codepage $cp + } + } + } + } + return $sdict + } + +} + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::ansi::sauce::lib { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + tcl::namespace::path [tcl::namespace::parent] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +#tcl::namespace::eval punk::ansi::sauce::system { +#} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::ansi::sauce { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::ansi::sauce" + @package -name "punk::ansi::sauce" -help\ + "Basic support for SAUCE format + Standard Architecture for Universal Comment Extensions + https://www.acid.org/info/sauce/sauce.htm " + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::ansi::sauce + } + proc about_topics {} { + #info commands results are returned in an arbitrary order (like array keys) + set topic_funs [info commands [namespace current]::get_topic_*] + set about_topics [list] + foreach f $topic_funs { + set tail [namespace tail $f] + lappend about_topics [string range $tail [string length get_topic_] end] + } + #Adjust this function or 'default_topics' if a different order is required + return [lsort $about_topics] + } + proc default_topics {} {return [list Description *]} + + # ------------------------------------------------------------- + # get_topic_ functions add more to auto-include in about topics + # ------------------------------------------------------------- + proc get_topic_Description {} { + punk::args::lib::tstr [string trim { + package punk::ansi::sauce + ANSI SAUCE block processor + } \n] + } + proc get_topic_License {} { + return "MIT" + } + proc get_topic_Version {} { + return "$::punk::ansi::sauce::version" + } + proc get_topic_Contributors {} { + set authors {{"Julian Noble" }} + set contributors "" + foreach a $authors { + append contributors $a \n + } + if {[string index $contributors end] eq "\n"} { + set contributors [string range $contributors 0 end-1] + } + return $contributors + } + proc get_topic_custom-topic {} { + punk::args::lib::tstr -return string { + A custom + topic + etc + } + } + # ------------------------------------------------------------- + } + + # we re-use the argument definition from punk::args::standard_about and override some items + set overrides [dict create] + dict set overrides @id -id "::punk::ansi::sauce::about" + dict set overrides @cmd -name "punk::ansi::sauce::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::ansi::sauce + }] \n] + dict set overrides topic -choices [list {*}[punk::ansi::sauce::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::ansi::sauce::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict + set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] + lappend PUNKARGS [list $newdef] + proc about {args} { + package require punk::args + #standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on + set argd [punk::args::parse $args withid ::punk::ansi::sauce::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::ansi::sauce::argdoc {*}$opts {*}[dict get $values topic] + } +} +# end of sample 'about' function +# == === === === === === === === === === === === === === === + + +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punk::ansi::sauce +} +# ----------------------------------------------------------------------------- + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide punk::ansi::sauce [tcl::namespace::eval punk::ansi::sauce { + variable pkg punk::ansi::sauce + variable version + set version 999999.0a1.0 +}] +return + diff --git a/src/modules/punk/ansi/sauce-buildversion.txt b/src/modules/punk/ansi/sauce-buildversion.txt new file mode 100644 index 00000000..f47d01c8 --- /dev/null +++ b/src/modules/punk/ansi/sauce-buildversion.txt @@ -0,0 +1,3 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. diff --git a/src/modules/punk/lib-999999.0a1.0.tm b/src/modules/punk/lib-999999.0a1.0.tm index 796ad397..33ec32f1 100644 --- a/src/modules/punk/lib-999999.0a1.0.tm +++ b/src/modules/punk/lib-999999.0a1.0.tm @@ -140,6 +140,16 @@ tcl::namespace::eval punk::lib::check { set bug [expr {![catch {regexp {} [error should_error]}]}] return [dict create bug $bug bugref cb03e57a description {regexp emptystring first argument over-optimised - difference in compiled vs traced behaviour.} level minor] } + proc has_tclbug_lsearch_sorted_inline_subindices {} { + if {[catch {lsearch -sorted -subindices -inline -index 0 {{a 1} {a 2} {b 3} {c 4} {c 5}} b} result]} { + #probably tcl version doesn't support all options + set bug 0 + } else { + set bug [expr {$result ne "b"}] + } + set description "lsearch -sorted with -subindices -inline - incorrect result." + return [dict create bug $bug bugref bc4ac0 description $description level minor] + } proc has_tclbug_script_var {} { set script {set j [list spud] ; list} @@ -765,7 +775,7 @@ namespace eval punk::lib { struct::list swap doesn't support 'end' offsets, and only sometimes appears to support basic expressions, depending on the expression compared to the list length." - @values -min 1 -max 1 + @values -min 3 -max 3 lvar -type string -help\ "name of list variable" a -type indexexpression @@ -995,11 +1005,8 @@ namespace eval punk::lib { e.g lzip {a b c d e} {1 2 3 4} {x y z} -> {a 1 x} {b 2 y} {c 3 z} {d 4 {}} {3 {} {}} " - @values -min 1 -max 1 - lvar -type string -help\ - "name of list variable" - a -type indexexpression - z -type indexexpression + @values -min 0 -max -1 + list -type list -multiple 1 -optional 1 }] } proc lzip {args} { diff --git a/src/modules/punk/nav/fs-999999.0a1.0.tm b/src/modules/punk/nav/fs-999999.0a1.0.tm index 9e7783b3..fba6e69a 100644 --- a/src/modules/punk/nav/fs-999999.0a1.0.tm +++ b/src/modules/punk/nav/fs-999999.0a1.0.tm @@ -171,7 +171,7 @@ tcl::namespace::eval punk::nav::fs { #It also seems common to cd when loading certain packages e.g tls from starkit. #While in most/normal cases the library will cd back to the remembered working directory after only a brief time - there seem to be many opportunities for issues #if the repl is used to launch/run a number of things in the one process - proc d/ {args} { + proc d/ {v args} { variable VIRTUAL_CWD set is_win [expr {"windows" eq $::tcl_platform(platform)}] @@ -211,17 +211,17 @@ tcl::namespace::eval punk::nav::fs { #result for glob is count of matches - use dirfiles etc for script access to results - set result [list location $location dircount $dircount filecount $filecount symlinks $symlinkcount] + set resultsummary [list location $location dircount $dircount filecount $filecount symlinks $symlinkcount] set filesizes [dict get $matchinfo filesizes] if {[llength $filesizes]} { set filesizes [lsearch -all -inline -not $filesizes na] set filebytes [tcl::mathop::+ {*}$filesizes] - lappend result filebytes [punk::lib::format_number $filebytes] + lappend resultsummary filebytes [punk::lib::format_number $filebytes] } if {[punk::nav::fs::system::codethread_is_running]} { if {[llength [info commands ::punk::console::titleset]]} { #if ansi is off - punk::console::titleset will try 'local' api method - which can fail - catch {::punk::console::titleset [lrange $result 1 end]} + catch {::punk::console::titleset [lrange $resultsummary 1 end]} } } if {[string match //zipfs:/* $location]} { @@ -229,21 +229,32 @@ tcl::namespace::eval punk::nav::fs { } else { set stripbase 1 } + if {$v eq "/"} { + #hack + dict set matchinfo files {} + dict set matchinfo filesizes {} + } set out [dirfiles_dict_as_lines -stripbase $stripbase $matchinfo] - set chunklist [list] - lappend chunklist [list stdout "[punk::ansi::a+ brightwhite]$out[punk::ansi::a]\n"] + #set chunklist [list] + #lappend chunklist [list stdout "[punk::ansi::a+ brightwhite]$out[punk::ansi::a]\n"] + set result "[punk::ansi::a+ brightwhite]$out[punk::ansi::a]\n" + append result $resultsummary + + if {[file normalize $VIRTUAL_CWD] ne [pwd]} { - lappend chunklist [list stderr "[punk::ansi::a+ red]PWD:[pwd] VIRTUAL_CWD:$VIRTUAL_CWD[punk::ansi::a]"] - } - lappend chunklist [list result $result] - if {$repl_runid != 0} { - if {![tsv::llength repl runchunks-$repl_runid]} { - #set ::punk::last_run_display $chunklist - tsv::lappend repl runchunks-$repl_runid {*}$chunklist - } - } else { - punk::nav::fs::system::emit_chunklist $chunklist + #lappend chunklist [list stderr "[punk::ansi::a+ red]PWD:[pwd] VIRTUAL_CWD:$VIRTUAL_CWD[punk::ansi::a]"] + puts stderr "[punk::ansi::a+ red]PWD:[pwd] VIRTUAL_CWD:$VIRTUAL_CWD[punk::ansi::a]" } + #lappend chunklist [list result $result] + + #if {$repl_runid != 0} { + # if {![tsv::llength repl runchunks-$repl_runid]} { + # #set ::punk::last_run_display $chunklist + # tsv::lappend repl runchunks-$repl_runid {*}$chunklist + # } + #} else { + # punk::nav::fs::system::emit_chunklist $chunklist + #} #puts stdout "-->[ansistring VIEW $result]" return $result } else { @@ -258,7 +269,7 @@ tcl::namespace::eval punk::nav::fs { if {$VIRTUAL_CWD eq "//zipfs:/" && ![string match //zipfs:/* [pwd]]} { #exit back to last nonzipfs path that was in use set VIRTUAL_CWD [pwd] - tailcall punk::nav::fs::d/ + tailcall punk::nav::fs::d/ $v } #we need to use normjoin to allow navigation to //server instead of just to //server/share (//server browsing unimplemented - review) @@ -277,7 +288,7 @@ tcl::namespace::eval punk::nav::fs { cd $up1 #set VIRTUAL_CWD [file normalize $a1] } - tailcall punk::nav::fs::d/ + tailcall punk::nav::fs::d/ $v } } @@ -287,7 +298,7 @@ tcl::namespace::eval punk::nav::fs { if {[file type $a1] eq "directory"} { cd $a1 #set VIRTUAL_CWD $a1 - tailcall punk::nav::fs::d/ + tailcall punk::nav::fs::d/ $v } } } @@ -297,7 +308,7 @@ tcl::namespace::eval punk::nav::fs { if {[file type $a1] eq "directory"} { cd $a1 #set VIRTUAL_CWD [file normalize $a1] - tailcall punk::nav::fs::d/ + tailcall punk::nav::fs::d/ $v } } @@ -321,7 +332,7 @@ tcl::namespace::eval punk::nav::fs { set VIRTUAL_CWD $target } } - tailcall punk::nav::fs::d/ + tailcall punk::nav::fs::d/ $v } set curdir $VIRTUAL_CWD } else { @@ -334,7 +345,7 @@ tcl::namespace::eval punk::nav::fs { set searchspec [lindex $args 0] set result "" - set chunklist [list] + #set chunklist [list] #Only merge results if location matches previous (caller can deliberately intersperse bogus globs to force split if desired) #TODO - remove duplicate file or dir items for overlapping patterns in same location!!! (at least for count, filebyte totals if not for display) @@ -389,7 +400,7 @@ tcl::namespace::eval punk::nav::fs { #emit previous result if {[dict size $this_result]} { dict set this_result filebytes [punk::lib::format_number [dict get $this_result filebytes]] - lappend chunklist [list result $this_result] + #lappend chunklist [list result $this_result] if {$result ne ""} { append result \n } @@ -424,33 +435,41 @@ tcl::namespace::eval punk::nav::fs { } set out [dirfiles_dict_as_lines -stripbase $stripbase $matchinfo] - lappend chunklist [list stdout "[punk::ansi::a+ brightwhite]$out[punk::ansi::a]\n"] + #lappend chunklist [list stdout "[punk::ansi::a+ brightwhite]$out[punk::ansi::a]\n"] + if {$result ne ""} { + append result \n + } + append result "[punk::ansi::a+ brightwhite]$out[punk::ansi::a]\n" + set last_location $location } #process final result if {[dict size $this_result]} { dict set this_result filebytes [punk::lib::format_number [dict get $this_result filebytes]] - lappend chunklist [list result $this_result] + #lappend chunklist [list result $this_result] if {$result ne ""} { append result \n } append result $this_result } + + if {[file normalize $VIRTUAL_CWD] ne [pwd]} { - lappend chunklist [list stderr "[punk::ansi::a+ red]PWD:[pwd] VIRTUAL_CWD:$VIRTUAL_CWD[punk::ansi::a]"] + #lappend chunklist [list stderr "[punk::ansi::a+ red]PWD:[pwd] VIRTUAL_CWD:$VIRTUAL_CWD[punk::ansi::a]"] + puts stderr "[punk::ansi::a+ red]PWD:[pwd] VIRTUAL_CWD:$VIRTUAL_CWD[punk::ansi::a]" } - if {[punk::nav::fs::system::codethread_is_running]} { - if {![tsv::llength repl runchunks-$repl_runid]} { - #set ::punk::last_run_display $chunklist - tsv::lappend repl runchunks-$repl_runid {*}$chunklist - } - } - if {$repl_runid == 0} { - punk::nav::fs::system::emit_chunklist $chunklist - } + #if {[punk::nav::fs::system::codethread_is_running]} { + # if {![tsv::llength repl runchunks-$repl_runid]} { + # #set ::punk::last_run_display $chunklist + # tsv::lappend repl runchunks-$repl_runid {*}$chunklist + # } + #} + #if {$repl_runid == 0} { + # punk::nav::fs::system::emit_chunklist $chunklist + #} return $result } } @@ -1479,6 +1498,26 @@ tcl::namespace::eval punk::nav::fs::system { } } + +interp alias {} ./ {} punk::nav::fs::d/ / +interp alias {} d/ {} punk::nav::fs::d/ / +interp alias {} .// {} punk::nav::fs::d/ // +interp alias {} d// {} punk::nav::fs::d/ // + +interp alias {} ../ {} punk::nav::fs::dd/ +interp alias {} dd/ {} punk::nav::fs::dd/ + +interp alias {} vwd {} punk::nav::fs::vwd ;#return punk::nav::fs::VIRTUAL_CWD - and report to stderr pwd if different +interp alias {} dirlist {} punk::nav::fs::dirlist +interp alias {} dirfiles {} punk::nav::fs::dirfiles +interp alias {} dirfiles_dict {} punk::nav::fs::dirfiles_dict + +interp alias {} ./new {} punk::nav::fs::d/new +interp alias {} d/new {} punk::nav::fs::d/new +interp alias {} ./~ {} punk::nav::fs::d/~ +interp alias {} d/~ {} punk::nav::fs::d/~ +interp alias {} x/ {} punk::nav::fs::x/ + # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ ## Ready package provide punk::nav::fs [tcl::namespace::eval punk::nav::fs { diff --git a/src/modules/punk/nav/ns-999999.0a1.0.tm b/src/modules/punk/nav/ns-999999.0a1.0.tm new file mode 100644 index 00000000..3ab5a6be --- /dev/null +++ b/src/modules/punk/nav/ns-999999.0a1.0.tm @@ -0,0 +1,302 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -buildversion.txt +# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.4.tm +# +# 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) 2025 +# +# @@ Meta Begin +# Application punk::nav::ns 999999.0a1.0 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +package require Tcl 8.6- + + + +tcl::namespace::eval punk::nav::ns { + variable PUNKARGS + variable ns_current + #allow presetting + if {![info exists ::punk::nav::ns::ns_current]} { + set ns_current :: + } + namespace path {::punk::ns} + + proc ns/ {v {ns_or_glob ""} args} { + variable ns_current ;#change active ns of repl by setting ns_current + + set ns_caller [uplevel 1 {::tcl::namespace::current}] + #puts stderr "ns_cur:$ns_current ns_call:$ns_caller" + + + set types [list all] + set nspathcommands 0 + if {$v eq "/"} { + set types [list children] + } + if {$v eq "///"} { + set nspathcommands 1 + } + + set ns_or_glob [string map {:::: ::} $ns_or_glob] + + #todo - cooperate with repl? + set out "" + if {$ns_or_glob eq ""} { + set is_absolute 1 + set ns_queried $ns_current + set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $ns_current *]] + } else { + set is_absolute [string match ::* $ns_or_glob] + set has_globchars [regexp {[*?]} $ns_or_glob] ;#basic globs only? + if {$is_absolute} { + if {!$has_globchars} { + if {![nsexists $ns_or_glob]} { + error "cannot change to namespace $ns_or_glob" + } + set ns_current $ns_or_glob + set ns_queried $ns_current + tailcall ns/ $v "" + } else { + set ns_queried $ns_or_glob + set out [nslist -types $types -nspathcommands $nspathcommands $ns_or_glob] + } + } else { + if {!$has_globchars} { + set nsnext [nsjoin $ns_current $ns_or_glob] + if {![nsexists $nsnext]} { + error "cannot change to namespace $ns_or_glob" + } + set ns_current $nsnext + set ns_queried $nsnext + set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $nsnext *]] + } else { + set ns_queried [nsjoin $ns_current $ns_or_glob] + set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $ns_current $ns_or_glob]] + } + } + } + set ns_display "\n$ns_queried" + if {$ns_current eq $ns_queried} { + if {$ns_current in [info commands $ns_current] } { + if {![catch [list tcl::namespace::ensemble configure $ns_current] ensemble_info]} { + if {[llength $ensemble_info] > 0} { + #this namespace happens to match ensemble command. + #todo - keep cache of encountered ensembles from commands.. and examine namespace in the configure info. + set ns_display "\n[a+ yellow bold]$ns_current (ensemble)[a+]" + } + } + } + } + append out $ns_display + return $out + } + + #create possibly nested namespace structure - but only if not already existant + proc n/new {args} { + variable ns_current + if {![llength $args]} { + error "usage: :/new \[ ...\]" + } + set a1 [lindex $args 0] + set is_absolute [string match ::* $a1] + if {$is_absolute} { + set nspath [nsjoinall {*}$args] + } else { + if {[string match :* $a1]} { + puts stderr "n/new WARNING namespace with leading colon '$a1' is likely to have unexpected results" + } + set nspath [nsjoinall $ns_current {*}$args] + } + + set ns_exists [nseval [nsprefix $nspath] [list ::tcl::namespace::exists [nstail $nspath] ]] + + if {$ns_exists} { + error "Namespace $nspath already exists" + } + #tcl::namespace::eval [nsprefix $nspath] [list tcl::namespace::eval [nstail $nspath] {}] + nseval [nsprefix $nspath] [list ::tcl::namespace::eval [nstail $nspath] {}] + n/ $nspath + } + + #nn/ ::/ nsup/ - back up one namespace level + proc nsup/ {v args} { + variable ns_current + if {$ns_current eq "::"} { + puts stderr "Already at global namespace '::'" + } else { + set out "" + set nsq [nsprefix $ns_current] + if {$v eq "/"} { + set out [get_nslist -match [nsjoin $nsq *] -types [list children]] + } else { + set out [get_nslist -match [nsjoin $nsq *] -types [list all]] + } + #set out [nslist [nsjoin $nsq *]] + set ns_current $nsq + append out "\n$ns_current" + return $out + } + } + + + +} + + + +#extra slash implies more verbosity (ie display commands instead of just nschildren) +interp alias {} n/ {} punk::nav::ns::ns/ / +interp alias {} n// {} punk::nav::ns::ns/ // +interp alias {} n/// {} punk::nav::ns::ns/ /// +interp alias {} n/new {} punk::nav::ns::n/new +interp alias {} nn/ {} punk::nav::ns::nsup/ / +interp alias {} nn// {} punk::nav::ns::nsup/ // +if 0 { +#we can't have ::/ without just plain / which is confusing. +interp alias {} :/ {} punk::nav::ns::ns/ / +interp alias {} :// {} punk::nav::ns::ns/ // +interp alias {} :/new {} punk::nav::ns::n/new +interp alias {} ::/ {} punk::nav::ns::nsup/ / +interp alias {} ::// {} punk::nav::ns::nsup/ // +} + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::nav::ns::lib { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + tcl::namespace::path [tcl::namespace::parent] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +#tcl::namespace::eval punk::nav::ns::system { +#} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::nav::ns { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::nav::ns" + @package -name "punk::nav::ns" -help\ + "Package + Description" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::nav::ns + } + proc about_topics {} { + #info commands results are returned in an arbitrary order (like array keys) + set topic_funs [info commands [namespace current]::get_topic_*] + set about_topics [list] + foreach f $topic_funs { + set tail [namespace tail $f] + lappend about_topics [string range $tail [string length get_topic_] end] + } + #Adjust this function or 'default_topics' if a different order is required + return [lsort $about_topics] + } + proc default_topics {} {return [list Description *]} + + # ------------------------------------------------------------- + # get_topic_ functions add more to auto-include in about topics + # ------------------------------------------------------------- + proc get_topic_Description {} { + punk::args::lib::tstr [string trim { + package punk::nav::ns + description to come.. + } \n] + } + proc get_topic_License {} { + return "MIT" + } + proc get_topic_Version {} { + return "$::punk::nav::ns::version" + } + proc get_topic_Contributors {} { + set authors {} + set contributors "" + foreach a $authors { + append contributors $a \n + } + if {[string index $contributors end] eq "\n"} { + set contributors [string range $contributors 0 end-1] + } + return $contributors + } + proc get_topic_custom-topic {} { + punk::args::lib::tstr -return string { + A custom + topic + etc + } + } + # ------------------------------------------------------------- + } + + # we re-use the argument definition from punk::args::standard_about and override some items + set overrides [dict create] + dict set overrides @id -id "::punk::nav::ns::about" + dict set overrides @cmd -name "punk::nav::ns::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::nav::ns + }] \n] + dict set overrides topic -choices [list {*}[punk::nav::ns::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::nav::ns::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict + set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] + lappend PUNKARGS [list $newdef] + proc about {args} { + package require punk::args + #standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on + set argd [punk::args::parse $args withid ::punk::nav::ns::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::nav::ns::argdoc {*}$opts {*}[dict get $values topic] + } +} +# end of sample 'about' function +# == === === === === === === === === === === === === === === + + +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punk::nav::ns +} +# ----------------------------------------------------------------------------- + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide punk::nav::ns [tcl::namespace::eval punk::nav::ns { + variable pkg punk::nav::ns + variable version + set version 999999.0a1.0 +}] +return + diff --git a/src/modules/punk/nav/ns-buildversion.txt b/src/modules/punk/nav/ns-buildversion.txt new file mode 100644 index 00000000..f47d01c8 --- /dev/null +++ b/src/modules/punk/nav/ns-buildversion.txt @@ -0,0 +1,3 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. diff --git a/src/modules/punk/ns-999999.0a1.0.tm b/src/modules/punk/ns-999999.0a1.0.tm index 1f57f61c..2f641dee 100644 --- a/src/modules/punk/ns-999999.0a1.0.tm +++ b/src/modules/punk/ns-999999.0a1.0.tm @@ -51,11 +51,11 @@ tcl::namespace::eval punk::ns { # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ tcl::namespace::eval punk::ns { - variable ns_current - #allow presetting - if {![info exists ::punk::ns::ns_current]} { - set ns_current :: - } + #variable ns_current + ##allow presetting + #if {![info exists ::punk::ns::ns_current]} { + # set ns_current :: + #} variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns namespace export nsjoin nsprefix nstail nsparts nseval nschildren nsimport_noclobber corp pkguse cmdtype synopsis @@ -68,127 +68,9 @@ tcl::namespace::eval punk::ns { #debug level punk.ns.compile 3 } - #leading colon makes it hard (impossible?) to call directly if not within the namespace - proc ns/ {v {ns_or_glob ""} args} { - variable ns_current ;#change active ns of repl by setting ns_current - - set ns_caller [uplevel 1 {::tcl::namespace::current}] - #puts stderr "ns_cur:$ns_current ns_call:$ns_caller" - set types [list all] - set nspathcommands 0 - if {$v eq "/"} { - set types [list children] - } - if {$v eq "///"} { - set nspathcommands 1 - } - set ns_or_glob [string map {:::: ::} $ns_or_glob] - - #todo - cooperate with repl? - set out "" - if {$ns_or_glob eq ""} { - set is_absolute 1 - set ns_queried $ns_current - set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $ns_current *]] - } else { - set is_absolute [string match ::* $ns_or_glob] - set has_globchars [regexp {[*?]} $ns_or_glob] ;#basic globs only? - if {$is_absolute} { - if {!$has_globchars} { - if {![nsexists $ns_or_glob]} { - error "cannot change to namespace $ns_or_glob" - } - set ns_current $ns_or_glob - set ns_queried $ns_current - tailcall ns/ $v "" - } else { - set ns_queried $ns_or_glob - set out [nslist -types $types -nspathcommands $nspathcommands $ns_or_glob] - } - } else { - if {!$has_globchars} { - set nsnext [nsjoin $ns_current $ns_or_glob] - if {![nsexists $nsnext]} { - error "cannot change to namespace $ns_or_glob" - } - set ns_current $nsnext - set ns_queried $nsnext - set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $nsnext *]] - } else { - set ns_queried [nsjoin $ns_current $ns_or_glob] - set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $ns_current $ns_or_glob]] - } - } - } - set ns_display "\n$ns_queried" - if {$ns_current eq $ns_queried} { - if {$ns_current in [info commands $ns_current] } { - if {![catch [list tcl::namespace::ensemble configure $ns_current] ensemble_info]} { - if {[llength $ensemble_info] > 0} { - #this namespace happens to match ensemble command. - #todo - keep cache of encountered ensembles from commands.. and examine namespace in the configure info. - set ns_display "\n[a+ yellow bold]$ns_current (ensemble)[a+]" - } - } - } - } - append out $ns_display - return $out - - - } - - - #create possibly nested namespace structure - but only if not already existant - proc n/new {args} { - variable ns_current - if {![llength $args]} { - error "usage: :/new \[ ...\]" - } - set a1 [lindex $args 0] - set is_absolute [string match ::* $a1] - if {$is_absolute} { - set nspath [nsjoinall {*}$args] - } else { - if {[string match :* $a1]} { - puts stderr "n/new WARNING namespace with leading colon '$a1' is likely to have unexpected results" - } - set nspath [nsjoinall $ns_current {*}$args] - } - - set ns_exists [nseval [nsprefix $nspath] [list ::tcl::namespace::exists [nstail $nspath] ]] - - if {$ns_exists} { - error "Namespace $nspath already exists" - } - #tcl::namespace::eval [nsprefix $nspath] [list tcl::namespace::eval [nstail $nspath] {}] - nseval [nsprefix $nspath] [list ::tcl::namespace::eval [nstail $nspath] {}] - n/ $nspath - } - - - #nn/ ::/ nsup/ - back up one namespace level - proc nsup/ {v args} { - variable ns_current - if {$ns_current eq "::"} { - puts stderr "Already at global namespace '::'" - } else { - set out "" - set nsq [nsprefix $ns_current] - if {$v eq "/"} { - set out [get_nslist -match [nsjoin $nsq *] -types [list children]] - } else { - set out [get_nslist -match [nsjoin $nsq *] -types [list all]] - } - #set out [nslist [nsjoin $nsq *]] - set ns_current $nsq - append out "\n$ns_current" - return $out - } - } #todo - walk up each ns - testing for possibly weirdly named namespaces #needed to use n/ to change to an oddly named namespace such as ":x" @@ -7152,7 +7034,7 @@ y" {return quirkykeyscript} tailcall apply [list args $scriptblock $ns] {*}$arglist } } else { - set out [punk::ns::ns/ / $ns] + set out [punk::nav::ns::ns/ / $ns] append out \n $ver return $out } @@ -7317,21 +7199,6 @@ y" {return quirkykeyscript} interp alias {} cmdtype {} punk::ns::cmdtype interp alias {} cmdtrace {} punk::ns::cmdtrace - #extra slash implies more verbosity (ie display commands instead of just nschildren) - interp alias {} n/ {} punk::ns::ns/ / - interp alias {} n// {} punk::ns::ns/ // - interp alias {} n/// {} punk::ns::ns/ /// - interp alias {} n/new {} punk::ns::n/new - interp alias {} nn/ {} punk::ns::nsup/ / - interp alias {} nn// {} punk::ns::nsup/ // - if 0 { - #we can't have ::/ without just plain / which is confusing. - interp alias {} :/ {} punk::ns::ns/ / - interp alias {} :// {} punk::ns::ns/ // - interp alias {} :/new {} punk::ns::n/new - interp alias {} ::/ {} punk::ns::nsup/ / - interp alias {} ::// {} punk::ns::nsup/ // - } interp alias {} corp {} punk::ns::corp diff --git a/src/modules/punk/repl-999999.0a1.0.tm b/src/modules/punk/repl-999999.0a1.0.tm index 34f009dc..9268455c 100644 --- a/src/modules/punk/repl-999999.0a1.0.tm +++ b/src/modules/punk/repl-999999.0a1.0.tm @@ -429,8 +429,8 @@ proc repl::start {inchan args} { } incr startinstance set loopinstance 0 - if {[info exists ::punk::ns::ns_current]} { - set start_in_ns $::punk::ns::ns_current + if {[info exists ::punk::nav::ns::ns_current]} { + set start_in_ns $::punk::nav::ns::ns_current } else { set start_in_ns :: } @@ -456,8 +456,8 @@ proc repl::start {inchan args} { interp eval code { namespace eval ::punk::repl::codethread {} set ::punk::repl::codethread::is_running 1 - namespace eval ::punk::ns::ns_current {} - set ::punk::ns::ns_current %ns1% + namespace eval ::punk::nav::ns::ns_current {} + set ::punk::nav::ns::ns_current %ns1% } }] set commandstr "" @@ -2060,7 +2060,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config } if {[chan eof $inputchan]} { rputs stderr "todo - attempt restart of repl on input channel: $inputchan in next loop" - catch {set ::punk::ns::ns_current "::"} + catch {set ::punk::nav::ns::ns_current "::"} #todo set flag to restart repl ? } else { rputs stderr "continuing.." @@ -2210,7 +2210,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config set lastrunchunks [tsv::get repl runchunks-[tsv::get repl runid]] append info "lastrunchunks\n" append info "chunks: [llength $lastrunchunks]\n" - append info "namespace: $::punk::ns::ns_current" + append info "namespace: $::punk::nav::ns::ns_current" debug_repl_emit $info } else { proc debug_repl_emit {msg} {return} @@ -2300,7 +2300,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config #set status [catch { # thread::send $ - # uplevel 1 {namespace inscope $::punk::ns::ns_current $run_command_string} + # uplevel 1 {namespace inscope $::punk::nav::ns::ns_current $run_command_string} #} raw_result] } @@ -2417,18 +2417,20 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config #- lindex $command would sometimes fail #if {[lindex $command 0] eq "runx"} {} + # set x [list\ + # [string equal -length [string length "d/ "] "d/ " $commandstr] || \ + # [string equal "d/\n" $commandstr] || \ + # [string equal -length [string length "dd/ "] "dd/ " $commandstr] || \ + # [string equal "dd/\n" $commandstr] || \ + # [string equal -length [string length "./ "] "./ " $commandstr] || \ + # [string equal "./\n" $commandstr] || \ + # [string equal -length [string length "../ "] "../ " $commandstr] || \ + # [string equal "../\n" $commandstr] || \ + # ] #temporary hack. #todo - use happy path return options for non-primary result (like www package) ? if { - [string equal -length [string length "d/ "] "d/ " $commandstr] || \ - [string equal "d/\n" $commandstr] || \ - [string equal -length [string length "dd/ "] "dd/ " $commandstr] || \ - [string equal "dd/\n" $commandstr] || \ - [string equal -length [string length "./ "] "./ " $commandstr] || \ - [string equal "./\n" $commandstr] || \ - [string equal -length [string length "../ "] "../ " $commandstr] || \ - [string equal "../\n" $commandstr] || \ [string equal -length [string length "runx "] "runx " $commandstr] || \ [string equal -length [string length "sh_runx "] "sh_runx " $commandstr] || \ [string equal -length [string length "runout "] "runout " $commandstr] || \ @@ -2716,7 +2718,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config } if {[chan eof $inputchan]} { rputs stderr "todo - attempt restart of repl on input channel: $inputchan in next loop" - catch {set ::punk::ns::ns_current "::"} + catch {set ::punk::nav::ns::ns_current "::"} #todo set flag to restart repl ? } else { rputs stderr "continuing.." diff --git a/src/modules/punk/repl/codethread-999999.0a1.0.tm b/src/modules/punk/repl/codethread-999999.0a1.0.tm index 51135a6e..c9198b77 100644 --- a/src/modules/punk/repl/codethread-999999.0a1.0.tm +++ b/src/modules/punk/repl/codethread-999999.0a1.0.tm @@ -158,20 +158,21 @@ tcl::namespace::eval punk::repl::codethread { if {[llength $::codeinterp::run_command_cache] > 2000} { set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache] } - if {[string first ":::" $::punk::ns::ns_current] >= 0} { + if {[string first ":::" $::punk::nav::ns::ns_current] >= 0} { #support for browsing 'odd' (inadvisable) namespaces #don't use 'namespace exists' - will conflate ::test::x with ::test:::x - #if {$::punk::ns::ns_current in [namespace children [punk::ns::nsprefix $::punk::ns::ns_current]} { + #if {$::punk::nav::ns::ns_current in [namespace children [punk::ns::nsprefix $::punk::nav::ns::ns_current]} { #} package require punk::ns - punk::ns::nseval_ifexists $::punk::ns::ns_current $::codeinterp::clonescript + package require punk::nav::ns + punk::ns::nseval_ifexists $::punk::nav::ns::ns_current $::codeinterp::clonescript } else { - if {![namespace exists $::punk::ns::ns_current]} { - namespace eval $::punk::ns::ns_current { - puts stderr "Created namespace: $::punk::ns::ns_current" + if {![namespace exists $::punk::nav::ns::ns_current]} { + namespace eval $::punk::nav::ns::ns_current { + puts stderr "Created namespace: $::punk::nav::ns::ns_current" } } - tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript + tcl::namespace::inscope $::punk::nav::ns::ns_current $::codeinterp::clonescript } } } result] diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm index 3d9988b1..3ec3ad9c 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm @@ -4072,7 +4072,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu # \033 - octal. equivalently \x1b in hex which is more common in documentation # empty list [a] should do reset - same for [a nonexistant] # explicit reset at beginning of parameter list for a= (as opposed to a+) - set t [linsert $t[unset t] 0 0] + #set t [linsert $t[unset t] 0 0] + ledit t -1 -1 0 if {![llength $e]} { set result "\x1b\[[join $t {;}]m" } else { diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm index 390b34ae..c5b6ddd3 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm @@ -765,7 +765,7 @@ namespace eval punk::lib { struct::list swap doesn't support 'end' offsets, and only sometimes appears to support basic expressions, depending on the expression compared to the list length." - @values -min 1 -max 1 + @values -min 3 -max 3 lvar -type string -help\ "name of list variable" a -type indexexpression @@ -995,11 +995,8 @@ namespace eval punk::lib { e.g lzip {a b c d e} {1 2 3 4} {x y z} -> {a 1 x} {b 2 y} {c 3 z} {d 4 {}} {3 {} {}} " - @values -min 1 -max 1 - lvar -type string -help\ - "name of list variable" - a -type indexexpression - z -type indexexpression + @values -min 0 -max -1 + list -type list -multiple 1 -optional 1 }] } proc lzip {args} { diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm index 157e8f30..997ea3c3 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm @@ -294,7 +294,8 @@ namespace eval punk::path { } } elseif {[lindex $parts 0] ne ""} { #relpath a/b/c - set parts [linsert $parts 0 .] + #set parts [linsert $parts 0 .] + ledit parts -1 -1 . set rootindex 0 #allow backtracking arbitrarily for leading .. entries - simplify where possible #also need to stop possible conversion to absolute path @@ -1091,7 +1092,8 @@ namespace eval punk::path { # loc is: ref/sub = sub while {$reference_len > 0} { - set location [linsert $location 0 ..] + #set location [linsert $location 0 ..] + ledit location -1 -1 .. incr reference_len -1 } set location [file join {*}$location] diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.3.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.3.tm index 4079254e..abef420d 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.3.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.3.tm @@ -5400,7 +5400,8 @@ tcl::namespace::eval textblock { l-2 { if {$lnum == 0} { if {[lindex $line_chunks 0] eq ""} { - set line_chunks [linsert $line_chunks 2 $pad] + #set line_chunks [linsert $line_chunks 2 $pad] + ledit line_chunks 2 1 $pad } else { #set line_chunks [linsert $line_chunks 0 $pad] ledit line_chunks -1 -1 $pad diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm index 3d9988b1..3ec3ad9c 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm @@ -4072,7 +4072,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu # \033 - octal. equivalently \x1b in hex which is more common in documentation # empty list [a] should do reset - same for [a nonexistant] # explicit reset at beginning of parameter list for a= (as opposed to a+) - set t [linsert $t[unset t] 0 0] + #set t [linsert $t[unset t] 0 0] + ledit t -1 -1 0 if {![llength $e]} { set result "\x1b\[[join $t {;}]m" } else { diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm index 390b34ae..c5b6ddd3 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm @@ -765,7 +765,7 @@ namespace eval punk::lib { struct::list swap doesn't support 'end' offsets, and only sometimes appears to support basic expressions, depending on the expression compared to the list length." - @values -min 1 -max 1 + @values -min 3 -max 3 lvar -type string -help\ "name of list variable" a -type indexexpression @@ -995,11 +995,8 @@ namespace eval punk::lib { e.g lzip {a b c d e} {1 2 3 4} {x y z} -> {a 1 x} {b 2 y} {c 3 z} {d 4 {}} {3 {} {}} " - @values -min 1 -max 1 - lvar -type string -help\ - "name of list variable" - a -type indexexpression - z -type indexexpression + @values -min 0 -max -1 + list -type list -multiple 1 -optional 1 }] } proc lzip {args} { diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm index 157e8f30..997ea3c3 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm @@ -294,7 +294,8 @@ namespace eval punk::path { } } elseif {[lindex $parts 0] ne ""} { #relpath a/b/c - set parts [linsert $parts 0 .] + #set parts [linsert $parts 0 .] + ledit parts -1 -1 . set rootindex 0 #allow backtracking arbitrarily for leading .. entries - simplify where possible #also need to stop possible conversion to absolute path @@ -1091,7 +1092,8 @@ namespace eval punk::path { # loc is: ref/sub = sub while {$reference_len > 0} { - set location [linsert $location 0 ..] + #set location [linsert $location 0 ..] + ledit location -1 -1 .. incr reference_len -1 } set location [file join {*}$location] diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.3.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.3.tm index 4079254e..abef420d 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.3.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.3.tm @@ -5400,7 +5400,8 @@ tcl::namespace::eval textblock { l-2 { if {$lnum == 0} { if {[lindex $line_chunks 0] eq ""} { - set line_chunks [linsert $line_chunks 2 $pad] + #set line_chunks [linsert $line_chunks 2 $pad] + ledit line_chunks 2 1 $pad } else { #set line_chunks [linsert $line_chunks 0 $pad] ledit line_chunks -1 -1 $pad diff --git a/src/testansi/AT2DEMO.ANS.ZIP b/src/testansi/AT2DEMO.ANS.ZIP deleted file mode 100644 index 13aeea44..00000000 Binary files a/src/testansi/AT2DEMO.ANS.ZIP and /dev/null differ diff --git a/src/testansi/acdu0692.zip b/src/testansi/acdu0692.zip deleted file mode 100644 index 4329b0dc..00000000 Binary files a/src/testansi/acdu0692.zip and /dev/null differ diff --git a/src/testansi/ansitoon.zip b/src/testansi/ansitoon.zip deleted file mode 100644 index c59875f5..00000000 Binary files a/src/testansi/ansitoon.zip and /dev/null differ diff --git a/src/vendormodules/dictn-0.1.2.tm b/src/vendormodules/dictn-0.1.2.tm index 2ed2b1ef..5a7de769 100644 --- a/src/vendormodules/dictn-0.1.2.tm +++ b/src/vendormodules/dictn-0.1.2.tm @@ -25,17 +25,46 @@ namespace eval dictn { namespace export {[a-z]*} namespace ensemble create + + namespace eval argdoc { + variable PUNKARGS + #non-colour SGR codes + set I "\x1b\[3m" ;# [a+ italic] + set NI "\x1b\[23m" ;# [a+ noitalic] + set B "\x1b\[1m" ;# [a+ bold] + set N "\x1b\[22m" ;# [a+ normal] + set T "\x1b\[1\;4m" ;# [a+ bold underline] + set NT "\x1b\[22\;24m\x1b\[4:0m" ;# [a+ normal nounderline] + } } ## ::dictn::append -#This can of course 'ruin' a nested dict if applied to the wrong element -# - i.e using the string op 'append' on an element that is itself a nested dict is analogous to the standard Tcl: -# %set list {a b {c d}} -# %append list x -# a b {c d}x -# IOW - don't do that unless you really know that's what you want. # +tcl::namespace::eval ::dictn::argdoc { + lappend PUNKARGS [list { + @id -id ::dictn::append + @cmd -name dictn::append\ + -summary\ + "Append a single string to the value at dict path."\ + -help\ + "Append a single string to the value at a given dictionary path. + + This can of course 'ruin' a nested dict if applied to the wrong element + - i.e using the string op 'append' on an element that is itself a nested dict is analogous to the standard Tcl: + %set list {a b {c d}} + %append list x + a b {c d}x + IOW - don't do that unless you really know that's what you want. + + Note than unlike dict append - only a single value is accepted for appending. + " + @values -min 2 -max 3 + dictvar -type string + path -type list + value -type any -default "" -optional 1 + }] +} proc ::dictn::append {dictvar path {value {}}} { if {[llength $path] == 1} { uplevel 1 [list dict append $dictvar $path $value] @@ -43,7 +72,7 @@ proc ::dictn::append {dictvar path {value {}}} { upvar 1 $dictvar dvar ::set str [dict get $dvar {*}$path] - append str $val + append str $value dict set dvar {*}$path $str } } @@ -73,6 +102,25 @@ proc ::dictn::for {keyvalvars dictval path body} { proc ::dictn::get {dictval {path {}}} { return [dict get $dictval {*}$path] } +tcl::namespace::eval ::dictn::argdoc { + lappend PUNKARGS [list { + @id -id ::dictn::getn + @cmd -name dictn::getn\ + -summary\ + "Get one or more paths in a dict simultaneously."\ + -help\ + "" + @values -min 1 -max -1 + dictvar -type string + path -type list -multiple 1 + }] +} +proc ::dictn::getn {dictval args} { + if {![llength $args]} { + return [::tcl::dict::get $dictval] + } + lmap path $args {::tcl::dict::get $dictval {*}$path} +} if {[info commands ::tcl::dict::getdef] ne ""} { @@ -85,10 +133,18 @@ if {[info commands ::tcl::dict::getdef] ne ""} { return [dict getdef $dictval {*}$path $default] } - proc ::dictn::incr {dictvar path {increment {}} } { - if {$increment eq ""} { - ::set increment 1 + proc ::dictn::incr {dictvar path {increment 1} } { + upvar 1 $dictvar dvar + if {[llength $path] == 1} { + return [::tcl::dict::incr dvar $path $increment] + } + if {[::tcl::info::exists dvar]} { + ::set increment [expr {[::tcl::dict::getdef $dvar {*}$path 0] + $increment}] } + return [::tcl::dict::set dvar {*}$path $increment] + } + #test - compare disassembly + proc ::dictn::incr2 {dictvar path {increment 1} } { if {[llength $path] == 1} { uplevel 1 [list dict incr $dictvar $path $increment] } else { @@ -233,6 +289,33 @@ proc ::dictn::set {dictvar path newval} { return [dict set dvar {*}$path $newval] } +tcl::namespace::eval ::dictn::argdoc { + lappend PUNKARGS [list { + @id -id ::dictn::setn + @cmd -name dictn::setn\ + -summary\ + "Set one or more paths in a dict to value(s)"\ + -help\ + "" + @values -min 3 -max -1 + dictvar -type string + path_newval -type {path newval} -multiple 1 + }] +} +proc ::dictn::setn {dictvar args} { + if {[llength $args] == 0} { + error "dictn::setn requires at least one pair" + } + if {[llength $args] % 2 != 0} { + error "dictn::setn requires trailing pairs" + } + upvar 1 $dictvar dvar + foreach {p v} $args { + ::tcl::dict::set dvar {*}$p $v + } + return $dvar +} + proc ::dictn::size {dictval {path {}}} { return [dict size [dict get $dictval {*}$path]] } @@ -312,6 +395,46 @@ proc ::dictn::values {dictval {path {}} {glob {}}} { } } +tcl::namespace::eval ::dictn::argdoc { + lappend PUNKARGS [list { + @id -id ::dictn::with + @cmd -name dictn::with\ + -summary\ + "Execute script for each key at dict path."\ + -help\ + "Execute the Tcl script in body with the value for each key within the + given key-path mapped to either variables or keys in a specified array. + + If the name of an array variable is not supplied for arrayvar, + dictn with behaves like dict with, except that it accepts a list + for the possibly nested key-path instead of separate arguments. + + The subkeys of the dict at the given key-path will create variables + in the calling scope. + + If an arrayvar is passed, an array of that name in the calling + scope will be populated with keys and values from the subkeys and + values of the dict at the given key-path." + @form -form standard + @values -min 3 -max 3 + dictvar -type string + path -type list + body -type string + + @form -form array + @values -min 4 -max 4 + dictvar -type string + path -type list + arrayvar -type string -help\ + "Name of array variable in which key values are + stored for the given dict path. + This prevents key values being used as variable + names in the calling scope, instead capturing them + as keys in the single specified array at the calling + scope." + body -type string + }] +} # Standard form: #'dictn with dictVariable path body' # @@ -351,7 +474,10 @@ proc ::dictn::with {dictvar path args} { - +::tcl::namespace::eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::dictn +} diff --git a/src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm b/src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm index 3d9988b1..3ec3ad9c 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm @@ -4072,7 +4072,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu # \033 - octal. equivalently \x1b in hex which is more common in documentation # empty list [a] should do reset - same for [a nonexistant] # explicit reset at beginning of parameter list for a= (as opposed to a+) - set t [linsert $t[unset t] 0 0] + #set t [linsert $t[unset t] 0 0] + ledit t -1 -1 0 if {![llength $e]} { set result "\x1b\[[join $t {;}]m" } else { diff --git a/src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.5.tm b/src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.5.tm index 390b34ae..c5b6ddd3 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.5.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.5.tm @@ -765,7 +765,7 @@ namespace eval punk::lib { struct::list swap doesn't support 'end' offsets, and only sometimes appears to support basic expressions, depending on the expression compared to the list length." - @values -min 1 -max 1 + @values -min 3 -max 3 lvar -type string -help\ "name of list variable" a -type indexexpression @@ -995,11 +995,8 @@ namespace eval punk::lib { e.g lzip {a b c d e} {1 2 3 4} {x y z} -> {a 1 x} {b 2 y} {c 3 z} {d 4 {}} {3 {} {}} " - @values -min 1 -max 1 - lvar -type string -help\ - "name of list variable" - a -type indexexpression - z -type indexexpression + @values -min 0 -max -1 + list -type list -multiple 1 -optional 1 }] } proc lzip {args} { diff --git a/src/vfs/_vfscommon.vfs/modules/punk/path-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/path-0.1.0.tm index 157e8f30..997ea3c3 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/path-0.1.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/path-0.1.0.tm @@ -294,7 +294,8 @@ namespace eval punk::path { } } elseif {[lindex $parts 0] ne ""} { #relpath a/b/c - set parts [linsert $parts 0 .] + #set parts [linsert $parts 0 .] + ledit parts -1 -1 . set rootindex 0 #allow backtracking arbitrarily for leading .. entries - simplify where possible #also need to stop possible conversion to absolute path @@ -1091,7 +1092,8 @@ namespace eval punk::path { # loc is: ref/sub = sub while {$reference_len > 0} { - set location [linsert $location 0 ..] + #set location [linsert $location 0 ..] + ledit location -1 -1 .. incr reference_len -1 } set location [file join {*}$location] diff --git a/src/vfs/_vfscommon.vfs/modules/punk/safe-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/safe-0.1.0.tm index 7254fc59..b52c76c4 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/safe-0.1.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/safe-0.1.0.tm @@ -922,14 +922,18 @@ tcl::namespace::eval punk::safe::system { set where [lsearch -exact $access_path [info library]] if {$where < 0} { # not found, add it. - set access_path [linsert $access_path 0 [info library]] + #set access_path [linsert $access_path 0 [info library]] + ledit access_path -1 -1 [info library] Log $child "tcl_library was not in auto_path,\ added it to child's access_path" NOTICE } elseif {$where != 0} { # not first, move it first - set access_path [linsert \ - [lreplace $access_path $where $where] \ - 0 [info library]] + #set access_path [linsert \ + # [lreplace $access_path $where $where] \ + # 0 [info library]] + ledit access_path $where $where + ledit access_path -1 -1 [info library] + Log $child "tcl_libray was not in first in auto_path,\ moved it to front of child's access_path" NOTICE } diff --git a/src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm b/src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm index 4079254e..abef420d 100644 --- a/src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm +++ b/src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm @@ -5400,7 +5400,8 @@ tcl::namespace::eval textblock { l-2 { if {$lnum == 0} { if {[lindex $line_chunks 0] eq ""} { - set line_chunks [linsert $line_chunks 2 $pad] + #set line_chunks [linsert $line_chunks 2 $pad] + ledit line_chunks 2 1 $pad } else { #set line_chunks [linsert $line_chunks 0 $pad] ledit line_chunks -1 -1 $pad