From fac00026fb09e4b72ae41f029b4f8387d2a99ec3 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sat, 26 Jul 2025 03:32:49 +1000 Subject: [PATCH] ansi and make.tcl fixes --- src/bootsupport/modules/punk/args-0.2.tm | 6 +- src/bootsupport/modules/punkcheck-0.1.0.tm | 129 +++++++++++++----- src/bootsupport/modules/textblock-0.1.3.tm | 1 + src/make.tcl | 34 ++++- src/modules/punk/ansi-999999.0a1.0.tm | 36 ++++- src/modules/punk/args-999999.0a1.0.tm | 28 +++- src/modules/punk/args/tclcore-999999.0a1.0.tm | 4 +- src/modules/punk/repl-999999.0a1.0.tm | 3 +- src/modules/punkcheck-0.1.0.tm | 121 +++++++++++----- src/modules/textblock-999999.0a1.0.tm | 47 +++++-- .../custom/_project/punk.basic/src/make.tcl | 34 ++++- .../src/bootsupport/modules/punk/args-0.2.tm | 6 +- .../bootsupport/modules/punkcheck-0.1.0.tm | 129 +++++++++++++----- .../bootsupport/modules/textblock-0.1.3.tm | 1 + .../_project/punk.project-0.1/src/make.tcl | 34 ++++- .../src/bootsupport/modules/punk/args-0.2.tm | 6 +- .../bootsupport/modules/punkcheck-0.1.0.tm | 129 +++++++++++++----- .../bootsupport/modules/textblock-0.1.3.tm | 1 + .../_project/punk.shell-0.1/src/make.tcl | 34 ++++- .../_vfscommon.vfs/modules/punk/args-0.2.tm | 6 +- .../_vfscommon.vfs/modules/punkcheck-0.1.0.tm | 125 +++++++++++++---- .../_vfscommon.vfs/modules/textblock-0.1.3.tm | 1 + 22 files changed, 685 insertions(+), 230 deletions(-) diff --git a/src/bootsupport/modules/punk/args-0.2.tm b/src/bootsupport/modules/punk/args-0.2.tm index ab1ca020..baa5daec 100644 --- a/src/bootsupport/modules/punk/args-0.2.tm +++ b/src/bootsupport/modules/punk/args-0.2.tm @@ -4468,13 +4468,13 @@ tcl::namespace::eval punk::args { #error "punk::args::parse Even number of -flag val pairs required after arglist" #} - #Default the -errorstyle to enhanced - # (slowest on unhappy path - but probably clearest for playing with new APIs interactively) + #Default the -errorstyle to standard + # (slow on unhappy path - but probably clearest for playing with new APIs interactively) # - application devs should distribute a config file with an errorstyle override if desired. # - devs who prefer a different default for interactive use should create a config for it. (todo) set defaultopts [dict create\ -form {*}\ - -errorstyle enhanced\ + -errorstyle standard\ ] #todo - load override_errorstyle from configuration diff --git a/src/bootsupport/modules/punkcheck-0.1.0.tm b/src/bootsupport/modules/punkcheck-0.1.0.tm index e0bb8b65..451ad7a5 100644 --- a/src/bootsupport/modules/punkcheck-0.1.0.tm +++ b/src/bootsupport/modules/punkcheck-0.1.0.tm @@ -32,9 +32,16 @@ package require punk::mix::util #see following article regarding the many problems with using mtime for build-decisions: https://apenwarr.ca/log/20181113 # namespace eval punkcheck { + #namespace export\ + # uuid\ + # start_installer_event installfile_* namespace export\ uuid\ - start_installer_event installfile_* + installtrack\ + install\ + install_tm_files\ + install_non_tm_files\ + summarize_install_resultdict\ #antiglob_dir & antiglob_file entries match the pattern at any level - should not contain path separators variable default_antiglob_dir_core [list "#*" "_aside" "_build" ".git" ".fossil*"] @@ -48,7 +55,7 @@ namespace eval punkcheck { } if {!$has_twapi} { if {[catch {package require uuid} errM]} { - error "Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" + error "punkcheck: Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" } return [uuid::uuid generate] } else { @@ -150,6 +157,8 @@ namespace eval punkcheck { } } + #instances created by an installtrack object in method start_event + #also in installtrack constructor - to represent existing events from the .punkcheck data oo::class create installevent { variable o_id variable o_rel_sourceroot @@ -266,7 +275,7 @@ namespace eval punkcheck { set o_operation [string toupper $operation] if {$o_operation_start_ts ne ""} { - error "[self] targetset_tart $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." + error "[self] targetset_init $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." } set o_operation_start_ts [clock microseconds] set seconds [expr {$o_operation_start_ts / 1000000}] @@ -1241,7 +1250,17 @@ namespace eval punkcheck { lappend PUNKARGS [list { @id -id ::punkcheck::install @cmd -name ::punkcheck::install -help\ - "Unidirectional file transfer to possibly non-empty target folder." + "Unidirectional file transfer to possibly non-empty target folder. + This is the simpler form of the API, performing a transfer from one + directory tree to another, copying each file when changes in the source + file are detected. + Changes are detected by content checksum. The first install will record + source checksums in a .punkcheck file (ideally located at the root of the + target folder). Subsequent installs will compare stored checksums with + the current checksums of the source files. + For more advanced install operations, the object command installtrack + can be used to define install operations. e.g when the transfer is not + one-to-one and a target file depends on multiple source files." @leaders -min 2 -max 2 srcdir -type directory tgtdir -type directory @@ -1251,7 +1270,7 @@ namespace eval punkcheck { "Deepest subdirectory - use -1 for no limit." -createdir -type boolean -default 0 -help\ "Whether to create the folder at tgtdir. - Any required subdirectories are created regardless of this setting." + Any required subdirectories are created regardless of this setting." -createempty -type boolean -default 0 -help\ "Whether to create folders at target that had no matches for our glob" -glob -type string -default "*" -help\ @@ -1282,8 +1301,8 @@ namespace eval punkcheck { } -punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\ "The location of the .punkcheck file to track installations and checksums. - The default value 'target' is generally recommended. - Can also be an absolute path to a folder." + The default value 'target' is generally recommended. + Can also be an absolute path to a folder." -punkcheck_records -default "" -help\ "Empty string or a parsed TDL records structure. e.g @@ -1294,7 +1313,14 @@ namespace eval punkcheck { }" -installer -default "punkcheck::install" -help\ "A user nominated string that is stored in the .punkcheck file - This might be the name of a script or installation process." + This might be the name of a script or installation process." + -progresschannel -default none -type string -help\ + "Name of channel e.g stderr, stdout to which progress messages are written. + This includes the tree-like output consisting of dots (or green U) for each + file processed. As the number of files in a tree is not known beforehand, + it isn't useful for a percentage-based progress meter, but it could potentially + be used to drive a spinner if the textual data is not desired. + Setting to none or an invalid channel will deactivate the output." }] ## unidirectional file transfer to possibly non empty folder #default of -overwrite no-targets will only copy files that are missing at the target @@ -1343,6 +1369,7 @@ namespace eval punkcheck { -punkcheck_eventid "\uFFFF"\ -punkcheck_records ""\ -installer punkcheck::install\ + -progresschannel none\ ] if {([llength $args] %2) != 0} { @@ -1367,6 +1394,10 @@ namespace eval punkcheck { set fileglob [dict get $opts -glob] set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting set opt_createempty [dict get $opts -createempty] + set opt_progresschannel [dict get $opts -progresschannel] + if {$opt_progresschannel in {"" none} || [catch {chan configure $opt_progresschannel}]} { + set opt_progresschannel "" + } if {$CALLDEPTH == 0} { #expensive to normalize but we need to do it at least once @@ -1485,6 +1516,7 @@ namespace eval punkcheck { dict unset config -call-depth-internal dict unset config -max_depth dict unset config -subdirlist + dict unset config -progresschannel tcl::dict::for {k v} $config { if {$v eq "\uFFFF"} { dict unset config $k @@ -1602,7 +1634,7 @@ namespace eval punkcheck { #puts stdout "Current target dir: $current_target_dir" - set last_processed_dir "" + set last_depth "" foreach m $match_list { set new_tgt_cksum_info [list] set relative_target_path [file join $relative_target_dir $m] @@ -1620,23 +1652,6 @@ namespace eval punkcheck { if {$is_antipath} { continue } - #puts stdout " checking file : $current_source_dir/$m" - #give some output - but not too deep - #set thismatchdir [file dirname [file dirname [file join $srcdir $relative_source_dir]]] - #if {$last_processed_dir ne $thismatchdir} { - # puts stdout "\n checking files in $thismatchdir" - # set last_processed_dir $thismatchdir - #} else { - # puts -nonewline stdout . - #} - if {$last_processed_dir ne $CALLDEPTH} { - puts -nonewline stdout \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] - puts -nonewline stdout \n[string repeat " " $CALLDEPTH]. - flush stdout - set last_processed_dir $CALLDEPTH - } else { - puts -nonewline stdout . - } set ts_start [clock microseconds] set seconds [expr {$ts_start / 1000000}] @@ -1649,7 +1664,9 @@ namespace eval punkcheck { #change to use extract_or_create_fileset_record ? set existing_filerec_posn [dict get $fetch_filerec_result position] if {$existing_filerec_posn == -1} { - puts stdout "NO existing record for $punkcheck_target_relpath" + if {$opt_progresschannel ne ""} { + puts stdout "\nNO existing record for $punkcheck_target_relpath" + } set has_filerec 0 set new_filerec [dict create tag FILEINFO -targets $punkcheck_target_relpath] set filerec $new_filerec @@ -1667,14 +1684,24 @@ namespace eval punkcheck { unset new_install_record + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + if {$CALLDEPTH <=1} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] + } + flush $opt_progresschannel + ##set last_depth $CALLDEPTH ;# done down below + } + } set relative_source_path [lib::path_relative $punkcheck_folder $current_source_dir/$m] #puts stdout " rel_source: $relative_source_path" - if {[file pathtype $relative_source_path] ne "relative"} { + #if {[file pathtype $relative_source_path] ne "relative"} { + #REVIEW #different volume or root - } + #} #Note this isn't a recordlist function - so it doesn't purely operate on the records #this hits the filesystem for the sourcepath - gets checksums/timestamps depending on config. #It doesn't save to .punkcheck (the only punkcheck::installfile_ method which doesn't) @@ -1683,18 +1710,19 @@ namespace eval punkcheck { set ts2 [clock milliseconds] set diff [expr {$ts2 - $ts1}] if {$diff > 100} { + #todo -errorchannel set errprefix ">>> punkcheck:" - puts stderr "$errprefix performance warning: fetch_metadata for $m took $diff ms." + puts stderr "\n$errprefix performance warning: fetch_metadata for $m took $diff ms." set lb [lindex [dict get $filerec body] end] #puts stderr "$errprefix filerec last body record:$lb" set records [dict get $lb body] set lr [lindex $records end] set alg [dict get $lr -cksum_all_opts -cksum_algorithm] if {$alg eq "sha1"} { - puts "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" - puts "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" + puts stderr "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" + puts stderr "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" } else { - puts "$errprefix cksum_algorithm: $alg" + puts stderr "$errprefix cksum_algorithm: $alg" } } @@ -1710,6 +1738,7 @@ namespace eval punkcheck { } set is_skip 0 + set is_new 0 if {$overwrite_what eq "all-targets"} { file mkdir $current_target_dir #-------------------------------------------- @@ -1720,12 +1749,13 @@ namespace eval punkcheck { lappend files_copied $current_source_dir/$m } else { if {![file exists $current_target_dir/$m]} { - puts stderr "punkcheck: first copy to $current_target_dir/$m " + #puts stderr "punkcheck: first copy to $current_target_dir/$m " file mkdir $current_target_dir file copy $current_source_dir/$m $current_target_dir set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] lappend files_copied $current_source_dir/$m incr filecount_new + set is_new 1 } else { switch -- $overwrite_what { installedsourcechanged-targets { @@ -1747,14 +1777,16 @@ namespace eval punkcheck { } } synced-targets { + #disallow overwriting of target that has been modified by some other mechanism + #review if {[llength $changed]} { - #only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized) + #only overwrite if the target file checksum equals the last installed checksum (ie target is in sync with previous source-set and so hasn't been customized) set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] set is_target_unmodified_since_install 0 set target_cksum_compare "unknown" set latest_install_record [punkcheck::recordlist::file_record_latest_installrecord $filerec] ;#may be no such record - in which case we get an empty list if {[dict exists $latest_install_record -targets_cksums]} { - set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) + set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) if {[dict get $existing_tgt_cksum_info cksum] eq $last_install_cksum} { set is_target_unmodified_since_install 1 set target_cksum_compare "match" @@ -1797,6 +1829,7 @@ namespace eval punkcheck { + set ts_now [clock microseconds] set elapsed_us [expr {$ts_now - $ts_start}] @@ -1827,6 +1860,29 @@ namespace eval punkcheck { lset punkcheck_records $existing_filerec_posn $filerec } + + #------------------------------------------------------------ + if {$is_skip} { + set mark . + } else { + if {$is_new} { + set mark \x1b\[32\;1mN\x1b\[m + } else { + #updated + set mark \x1b\[32\;1mU\x1b\[m + } + } + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH]$mark + flush $opt_progresschannel + set last_depth $CALLDEPTH + } else { + puts -nonewline $opt_progresschannel $mark + } + } + #------------------------------------------------------------ + } if {$max_depth != -1 && $CALLDEPTH >= $max_depth} { @@ -1905,6 +1961,7 @@ namespace eval punkcheck { -punkcheck_folder $punkcheck_folder\ -punkcheck_eventid $punkcheck_eventid\ -punkcheck_records $punkcheck_records\ + -progresschannel $opt_progresschannel\ ] set sub_opts [dict merge $opts $sub_opts] set sub_result [punkcheck::install $srcdir $tgtdir {*}$sub_opts] diff --git a/src/bootsupport/modules/textblock-0.1.3.tm b/src/bootsupport/modules/textblock-0.1.3.tm index c89b3594..65c5aaa7 100644 --- a/src/bootsupport/modules/textblock-0.1.3.tm +++ b/src/bootsupport/modules/textblock-0.1.3.tm @@ -60,6 +60,7 @@ package require punk::ansi package require punk::lib catch {package require patternpunk} package require overtype +package require struct::set #safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list. #2025 - required term::ansi features for altg now built in to textblock diff --git a/src/make.tcl b/src/make.tcl index 853185bf..2de13afb 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -1653,7 +1653,7 @@ if {$::punkboot::command eq "bootsupport"} { file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists $boot_event targetset_started # -- --- --- --- --- --- - puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" + puts "\nBOOTSUPPORT module$which update: $srcfile -> $tgtfile" if {[catch { file copy -force $srcfile $tgtfile } errM]} { @@ -1719,7 +1719,8 @@ if {$::punkboot::command eq "bootsupport"} { set resultdict [punkcheck::install $sourcemodules $targetroot\ -overwrite installedsourcechanged-targets\ -antiglob_paths $antipaths\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] @@ -1737,7 +1738,8 @@ if {$::punkboot::command eq "bootsupport"} { -max_depth 1\ -createempty 0\ -overwrite installedsourcechanged-targets\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] flush stdout @@ -1784,7 +1786,12 @@ if {$::punkboot::command in {project modules}} { #install .tm *and other files* puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}] + set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder\ + -installer make.tcl\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths {README.md include_modules.config}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendormodulefolders]} { @@ -1810,7 +1817,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendorlibfolders]} { @@ -1906,7 +1917,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $projectlibfolders]} { @@ -1941,7 +1956,12 @@ if {$::punkboot::command in {project modules}} { set overwrite "installedsourcechanged-targets" #set overwrite "ALL-TARGETS" puts stdout "MODULEFOLDER non_tm_files $src_module_dir - copying to $target_modules_base (if source file changed)" - set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -antiglob_paths {README.md}] + set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base\ + -installer make.tcl\ + -overwrite $overwrite\ + -antiglob_paths {README.md}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } diff --git a/src/modules/punk/ansi-999999.0a1.0.tm b/src/modules/punk/ansi-999999.0a1.0.tm index 988574a7..cc74494e 100644 --- a/src/modules/punk/ansi-999999.0a1.0.tm +++ b/src/modules/punk/ansi-999999.0a1.0.tm @@ -3789,6 +3789,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set emit "" #set parts [punk::ansi::ta::split_codes $text] set parts [punk::ansi::ta::split_codes_single $text] + set prevcode "" foreach {pt code} $parts { switch -- [llength $codestack] { 0 { @@ -3809,6 +3810,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu } #parts ends on a pt - last code always empty string if {$code ne ""} { + set prevcode $code set c1c2 [tcl::string::range $code 0 1] set leadernorm [tcl::string::range [tcl::string::map [list\ \x1b\[ 7CSI\ @@ -3827,7 +3829,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set codestack [lremove $codestack {*}$dup_posns] lappend codestack $code } else { - + #jjtest + append emit $code } } 7GFX { @@ -3839,12 +3842,23 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set o_gx_state off } } + #jjtest + append emit $code } default { #other ansi codes + #jjtest + append emit $code } } - append emit $code + #jjtest + #append emit $code + } else { + #jjtest + #code is only empty when processing final pt + if {$pt eq ""} { + append emit $prevcode + } } } return [append emit $R] @@ -3882,6 +3896,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set emit "" #set parts [punk::ansi::ta::split_codes $text] set parts [punk::ansi::ta::split_codes_single $text] + set prevcode "" foreach {pt code} $parts { switch -- [llength $codestack] { 0 { @@ -3902,6 +3917,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu } #parts ends on a pt - last code always empty string if {$code ne ""} { + set prevcode $code set c1c2 [tcl::string::range $code 0 1] set leadernorm [tcl::string::range [tcl::string::map [list\ \x1b\[ 7CSI\ @@ -3920,7 +3936,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set codestack [lremove $codestack {*}$dup_posns] lappend codestack $code } else { - + #jjtest + apend emit $code } } 7GFX { @@ -3932,12 +3949,23 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu set o_gx_state off } } + #jjtest + append emit $code } default { #other ansi codes + #jjtest + append emit $code } } - append emit $code + #jjtest + #append emit $code + } else { + #jjtest + #code is only empty when processing final pt + if {$pt eq ""} { + append emit $prevcode + } } } return [append emit $R] diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index ca1d702e..0e82764e 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -3169,6 +3169,7 @@ tcl::namespace::eval punk::args { set arg_error_CLR(goodarg) [a+ green strike] set arg_error_CLR(goodchoice) [a+ reverse] set arg_error_CLR(linebase_header) [a+ white] + set arg_error_CLR(linebase) [a+ white] set arg_error_CLR(cmdname) [a+ brightwhite] set arg_error_CLR(groupname) [a+ bold] set arg_error_CLR(ansiborder) [a+ bold] @@ -3380,7 +3381,7 @@ tcl::namespace::eval punk::args { #set RST [a] - set RST "\x1b\[m" + set RST "\x1b\[0m" set t "" ;#possible oo table object - may be tested for objectiness at the end so needs to exist. #REVIEW - risk of accidental indefinite recursion if functions used here also use punk::args::get_dict and there is an argument error @@ -3442,7 +3443,8 @@ tcl::namespace::eval punk::args { if {$cmdhelp ne ""} { lappend blank_header_col "" #set cmdhelp_display [a+ brightwhite]$cmdhelp[a] - set cmdhelp_display [textblock::ansibase_lines $cmdhelp $CLR(linebase_header)] + #set cmdhelp_display [textblock::ansibase_lines $cmdhelp $CLR(linebase_header)] + set cmdhelp_display [punk::ansi::ansiwrap_raw $CLR(linebase_header) "" "" $cmdhelp] } else { set cmdhelp_display "" } @@ -3820,7 +3822,7 @@ tcl::namespace::eval punk::args { set choicelabeldict [Dict_getdef $arginfo -choicelabels {}] set choiceinfodict [Dict_getdef $arginfo -choiceinfo {}] set formattedchoices [dict create] ;#use dict rather than array to preserve order - append help " Choices$prefixmsg$casemsg" + append help "Choices$prefixmsg$casemsg" if {$choicemultiple_max > 1 || $choicemultiple_max == -1} { if {$choicemultiple_max == -1} { append help \n " The value can be a list of $choicemultiple_min or more of these choices" @@ -4040,8 +4042,16 @@ tcl::namespace::eval punk::args { $obj configure_column $i -blockalign left incr i } - append help \n[textblock::join -- " " [$obj print]] + #------------- + #todo - tests + #see special case double reset at end of content in textblock class table get_column_by_index + #bug fixed - needed to ensure last two resets were actually concurrent and at end. + #append help "\nbase[a+ green]ab\nc[a]base" ;#ok + #vs + #append help "\nbase[a+ green]a[a]b\nc[a]base" ;#not ok + #------------- + #set ansititle [dict get [$obj configure -title] value] $obj destroy } @@ -4239,9 +4249,13 @@ tcl::namespace::eval punk::args { if {[info object isa object $t]} { set result $t } + } else { + #put original error at bottom of table too + append result \n $msg } } else { set result $errmsg + append result \n $msg } if {$as_error} { #add PUNK to the tail end of the more usual -errorcode {TCL WRONGARGS} so we maintain reasonable compat with things looking for TCL WRONGARGS - but also differentiate it. @@ -4468,13 +4482,13 @@ tcl::namespace::eval punk::args { #error "punk::args::parse Even number of -flag val pairs required after arglist" #} - #Default the -errorstyle to enhanced - # (slowest on unhappy path - but probably clearest for playing with new APIs interactively) + #Default the -errorstyle to standard + # (slow on unhappy path - but probably clearest for playing with new APIs interactively) # - application devs should distribute a config file with an errorstyle override if desired. # - devs who prefer a different default for interactive use should create a config for it. (todo) set defaultopts [dict create\ -form {*}\ - -errorstyle enhanced\ + -errorstyle standard\ ] #todo - load override_errorstyle from configuration diff --git a/src/modules/punk/args/tclcore-999999.0a1.0.tm b/src/modules/punk/args/tclcore-999999.0a1.0.tm index e241e75b..5308dec1 100644 --- a/src/modules/punk/args/tclcore-999999.0a1.0.tm +++ b/src/modules/punk/args/tclcore-999999.0a1.0.tm @@ -3425,8 +3425,8 @@ tcl::namespace::eval punk::args::tclcore { @opts -type string -parsekey "" -group "NESTED LIST OPTIONS" -grouphelp\ "These options are used to search lists of lists. They may be used with any other options." -stride -type integer -default 1 -typesynopsis strideLength -help\ - "If this option is specified, the list is treated as consisting of groups of strideLength elements and the - groups are searched by either their first element or, if the -index option is used, by the element within + "If this option is specified, the list is treated as consisting of groups of ${$I}strideLength${$NI} elements and the + groups are searched by either their first element or, if the ${$B}-index${$N} option is used, by the element within each group given by the first index passed to -index (which is then ignored by -index). The resulting index always points to the first element in a group. The list length must be an integer multiple of strideLength, which in turn must be at least 1. A diff --git a/src/modules/punk/repl-999999.0a1.0.tm b/src/modules/punk/repl-999999.0a1.0.tm index d9e0390a..ba8447b1 100644 --- a/src/modules/punk/repl-999999.0a1.0.tm +++ b/src/modules/punk/repl-999999.0a1.0.tm @@ -2567,7 +2567,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config set c [a yellow bold] set n [a] - rputs stderr $c$result$n + #rputs stderr $c$result$n + rputs [punk::ansi::ansiwrap_raw $c "\x1b\[m" "" $result] #tcl err hint prompt - lowercase doprompt "p% " } diff --git a/src/modules/punkcheck-0.1.0.tm b/src/modules/punkcheck-0.1.0.tm index 4f1b6bb9..451ad7a5 100644 --- a/src/modules/punkcheck-0.1.0.tm +++ b/src/modules/punkcheck-0.1.0.tm @@ -32,9 +32,16 @@ package require punk::mix::util #see following article regarding the many problems with using mtime for build-decisions: https://apenwarr.ca/log/20181113 # namespace eval punkcheck { + #namespace export\ + # uuid\ + # start_installer_event installfile_* namespace export\ uuid\ - start_installer_event installfile_* + installtrack\ + install\ + install_tm_files\ + install_non_tm_files\ + summarize_install_resultdict\ #antiglob_dir & antiglob_file entries match the pattern at any level - should not contain path separators variable default_antiglob_dir_core [list "#*" "_aside" "_build" ".git" ".fossil*"] @@ -48,7 +55,7 @@ namespace eval punkcheck { } if {!$has_twapi} { if {[catch {package require uuid} errM]} { - error "Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" + error "punkcheck: Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" } return [uuid::uuid generate] } else { @@ -150,6 +157,8 @@ namespace eval punkcheck { } } + #instances created by an installtrack object in method start_event + #also in installtrack constructor - to represent existing events from the .punkcheck data oo::class create installevent { variable o_id variable o_rel_sourceroot @@ -266,7 +275,7 @@ namespace eval punkcheck { set o_operation [string toupper $operation] if {$o_operation_start_ts ne ""} { - error "[self] targetset_tart $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." + error "[self] targetset_init $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." } set o_operation_start_ts [clock microseconds] set seconds [expr {$o_operation_start_ts / 1000000}] @@ -1241,7 +1250,17 @@ namespace eval punkcheck { lappend PUNKARGS [list { @id -id ::punkcheck::install @cmd -name ::punkcheck::install -help\ - "Unidirectional file transfer to possibly non-empty target folder." + "Unidirectional file transfer to possibly non-empty target folder. + This is the simpler form of the API, performing a transfer from one + directory tree to another, copying each file when changes in the source + file are detected. + Changes are detected by content checksum. The first install will record + source checksums in a .punkcheck file (ideally located at the root of the + target folder). Subsequent installs will compare stored checksums with + the current checksums of the source files. + For more advanced install operations, the object command installtrack + can be used to define install operations. e.g when the transfer is not + one-to-one and a target file depends on multiple source files." @leaders -min 2 -max 2 srcdir -type directory tgtdir -type directory @@ -1251,7 +1270,7 @@ namespace eval punkcheck { "Deepest subdirectory - use -1 for no limit." -createdir -type boolean -default 0 -help\ "Whether to create the folder at tgtdir. - Any required subdirectories are created regardless of this setting." + Any required subdirectories are created regardless of this setting." -createempty -type boolean -default 0 -help\ "Whether to create folders at target that had no matches for our glob" -glob -type string -default "*" -help\ @@ -1282,8 +1301,8 @@ namespace eval punkcheck { } -punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\ "The location of the .punkcheck file to track installations and checksums. - The default value 'target' is generally recommended. - Can also be an absolute path to a folder." + The default value 'target' is generally recommended. + Can also be an absolute path to a folder." -punkcheck_records -default "" -help\ "Empty string or a parsed TDL records structure. e.g @@ -1294,7 +1313,14 @@ namespace eval punkcheck { }" -installer -default "punkcheck::install" -help\ "A user nominated string that is stored in the .punkcheck file - This might be the name of a script or installation process." + This might be the name of a script or installation process." + -progresschannel -default none -type string -help\ + "Name of channel e.g stderr, stdout to which progress messages are written. + This includes the tree-like output consisting of dots (or green U) for each + file processed. As the number of files in a tree is not known beforehand, + it isn't useful for a percentage-based progress meter, but it could potentially + be used to drive a spinner if the textual data is not desired. + Setting to none or an invalid channel will deactivate the output." }] ## unidirectional file transfer to possibly non empty folder #default of -overwrite no-targets will only copy files that are missing at the target @@ -1343,6 +1369,7 @@ namespace eval punkcheck { -punkcheck_eventid "\uFFFF"\ -punkcheck_records ""\ -installer punkcheck::install\ + -progresschannel none\ ] if {([llength $args] %2) != 0} { @@ -1367,6 +1394,10 @@ namespace eval punkcheck { set fileglob [dict get $opts -glob] set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting set opt_createempty [dict get $opts -createempty] + set opt_progresschannel [dict get $opts -progresschannel] + if {$opt_progresschannel in {"" none} || [catch {chan configure $opt_progresschannel}]} { + set opt_progresschannel "" + } if {$CALLDEPTH == 0} { #expensive to normalize but we need to do it at least once @@ -1485,6 +1516,7 @@ namespace eval punkcheck { dict unset config -call-depth-internal dict unset config -max_depth dict unset config -subdirlist + dict unset config -progresschannel tcl::dict::for {k v} $config { if {$v eq "\uFFFF"} { dict unset config $k @@ -1602,7 +1634,7 @@ namespace eval punkcheck { #puts stdout "Current target dir: $current_target_dir" - set last_processed_dir "" + set last_depth "" foreach m $match_list { set new_tgt_cksum_info [list] set relative_target_path [file join $relative_target_dir $m] @@ -1620,23 +1652,6 @@ namespace eval punkcheck { if {$is_antipath} { continue } - #puts stdout " checking file : $current_source_dir/$m" - #give some output - but not too deep - #set thismatchdir [file dirname [file dirname [file join $srcdir $relative_source_dir]]] - #if {$last_processed_dir ne $thismatchdir} { - # puts stdout "\n checking files in $thismatchdir" - # set last_processed_dir $thismatchdir - #} else { - # puts -nonewline stdout . - #} - if {$last_processed_dir ne $CALLDEPTH} { - puts -nonewline stdout \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] - puts -nonewline stdout \n[string repeat " " $CALLDEPTH]. - flush stdout - set last_processed_dir $CALLDEPTH - } else { - puts -nonewline stdout . - } set ts_start [clock microseconds] set seconds [expr {$ts_start / 1000000}] @@ -1649,7 +1664,9 @@ namespace eval punkcheck { #change to use extract_or_create_fileset_record ? set existing_filerec_posn [dict get $fetch_filerec_result position] if {$existing_filerec_posn == -1} { - puts stdout "NO existing record for $punkcheck_target_relpath" + if {$opt_progresschannel ne ""} { + puts stdout "\nNO existing record for $punkcheck_target_relpath" + } set has_filerec 0 set new_filerec [dict create tag FILEINFO -targets $punkcheck_target_relpath] set filerec $new_filerec @@ -1667,14 +1684,24 @@ namespace eval punkcheck { unset new_install_record + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + if {$CALLDEPTH <=1} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] + } + flush $opt_progresschannel + ##set last_depth $CALLDEPTH ;# done down below + } + } set relative_source_path [lib::path_relative $punkcheck_folder $current_source_dir/$m] #puts stdout " rel_source: $relative_source_path" - if {[file pathtype $relative_source_path] ne "relative"} { + #if {[file pathtype $relative_source_path] ne "relative"} { + #REVIEW #different volume or root - } + #} #Note this isn't a recordlist function - so it doesn't purely operate on the records #this hits the filesystem for the sourcepath - gets checksums/timestamps depending on config. #It doesn't save to .punkcheck (the only punkcheck::installfile_ method which doesn't) @@ -1683,6 +1710,7 @@ namespace eval punkcheck { set ts2 [clock milliseconds] set diff [expr {$ts2 - $ts1}] if {$diff > 100} { + #todo -errorchannel set errprefix ">>> punkcheck:" puts stderr "\n$errprefix performance warning: fetch_metadata for $m took $diff ms." set lb [lindex [dict get $filerec body] end] @@ -1710,6 +1738,7 @@ namespace eval punkcheck { } set is_skip 0 + set is_new 0 if {$overwrite_what eq "all-targets"} { file mkdir $current_target_dir #-------------------------------------------- @@ -1720,12 +1749,13 @@ namespace eval punkcheck { lappend files_copied $current_source_dir/$m } else { if {![file exists $current_target_dir/$m]} { - puts stderr "punkcheck: first copy to $current_target_dir/$m " + #puts stderr "punkcheck: first copy to $current_target_dir/$m " file mkdir $current_target_dir file copy $current_source_dir/$m $current_target_dir set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] lappend files_copied $current_source_dir/$m incr filecount_new + set is_new 1 } else { switch -- $overwrite_what { installedsourcechanged-targets { @@ -1747,14 +1777,16 @@ namespace eval punkcheck { } } synced-targets { + #disallow overwriting of target that has been modified by some other mechanism + #review if {[llength $changed]} { - #only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized) + #only overwrite if the target file checksum equals the last installed checksum (ie target is in sync with previous source-set and so hasn't been customized) set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] set is_target_unmodified_since_install 0 set target_cksum_compare "unknown" set latest_install_record [punkcheck::recordlist::file_record_latest_installrecord $filerec] ;#may be no such record - in which case we get an empty list if {[dict exists $latest_install_record -targets_cksums]} { - set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) + set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) if {[dict get $existing_tgt_cksum_info cksum] eq $last_install_cksum} { set is_target_unmodified_since_install 1 set target_cksum_compare "match" @@ -1797,6 +1829,7 @@ namespace eval punkcheck { + set ts_now [clock microseconds] set elapsed_us [expr {$ts_now - $ts_start}] @@ -1827,6 +1860,29 @@ namespace eval punkcheck { lset punkcheck_records $existing_filerec_posn $filerec } + + #------------------------------------------------------------ + if {$is_skip} { + set mark . + } else { + if {$is_new} { + set mark \x1b\[32\;1mN\x1b\[m + } else { + #updated + set mark \x1b\[32\;1mU\x1b\[m + } + } + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH]$mark + flush $opt_progresschannel + set last_depth $CALLDEPTH + } else { + puts -nonewline $opt_progresschannel $mark + } + } + #------------------------------------------------------------ + } if {$max_depth != -1 && $CALLDEPTH >= $max_depth} { @@ -1905,6 +1961,7 @@ namespace eval punkcheck { -punkcheck_folder $punkcheck_folder\ -punkcheck_eventid $punkcheck_eventid\ -punkcheck_records $punkcheck_records\ + -progresschannel $opt_progresschannel\ ] set sub_opts [dict merge $opts $sub_opts] set sub_result [punkcheck::install $srcdir $tgtdir {*}$sub_opts] diff --git a/src/modules/textblock-999999.0a1.0.tm b/src/modules/textblock-999999.0a1.0.tm index 39f1508e..ae286f36 100644 --- a/src/modules/textblock-999999.0a1.0.tm +++ b/src/modules/textblock-999999.0a1.0.tm @@ -60,6 +60,7 @@ package require punk::ansi package require punk::lib catch {package require patternpunk} package require overtype +package require struct::set #safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list. #2025 - required term::ansi features for altg now built in to textblock @@ -2533,24 +2534,26 @@ tcl::namespace::eval textblock { set parts [punk::ansi::ta::split_codes_single $c] #we have detected ansi - so there will always be at least 3 parts beginning and ending with pt pt,ansi,pt,ansi...,pt set codes [list] + set ptlens [list] foreach {pt cd} $parts { - if {$cd ne ""} { - lappend codes $cd - } + lappend codes $cd + lappend ptlens [string length $pt] } #set takebg [lindex $parts end-1] #set cell_bg [punk::ansi::codetype::sgr_merge_singles [list $takebg] -filter_fg 1] set cell_bg [punk::ansi::codetype::sgr_merge_singles $codes -filter_fg 1 -filter_reset 1] #puts --->[ansistring VIEW $codes] - if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end]]} { - if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end-1]]} { + if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end-1]]} { + if {[lindex $ptlens end] == 0 && [lindex $ptlens end-1] == 0 && [punk::ansi::codetype::is_sgr_reset [lindex $codes end-2]]} { #special case double reset at end of content - set cell_ansi_tail [punk::ansi::codetype::sgr_merge_singles $codes] ;#no filters + #TODO - write test! + set cell_ansi_tail [punk::ansi::codetype::sgr_merge_singles [lrange $codes 0 end-3]] ;#no filters set ansibase "" set row_ansibase "" if {$ftblock} { set ansiborder_final [punk::ansi::codetype::sgr_merge [list $ansiborder_body_col_row] -filter_bg 1] + set ansiborder_final [punk::ansi::codetype::sgr_merge [list $ansiborder_final $cell_bg]] } set cell_ansibase $cell_ansi_tail } else { @@ -2564,6 +2567,28 @@ tcl::namespace::eval textblock { } set cell_ansibase $cell_bg } + #v1 + #if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end]]} { + # if {[punk::ansi::codetype::is_sgr_reset [lindex $codes end-1]]} { + # #special case double reset at end of content + # set cell_ansi_tail [punk::ansi::codetype::sgr_merge_singles $codes] ;#no filters + # set ansibase "" + # set row_ansibase "" + # if {$ftblock} { + # set ansiborder_final [punk::ansi::codetype::sgr_merge [list $ansiborder_body_col_row] -filter_bg 1] + # } + # set cell_ansibase $cell_ansi_tail + # } else { + # #single trailing reset in content + # set cell_ansibase "" ;#cell doesn't contribute to frame's ansibase + # } + #} else { + # if {$ftblock} { + # #no resets - use cell's bg to extend to the border - only for block frames + # set ansiborder_final $ansiborder_body_col_row$cell_bg + # } + # set cell_ansibase $cell_bg + #} } set ansibase_final $ansibase$row_ansibase$cell_ansibase @@ -8628,8 +8653,11 @@ tcl::namespace::eval textblock { if {$opt_ansibase ne ""} { if {[punk::ansi::ta::detect $cache_inner]} { #set cache_inner [punk::ansi::ansiwrap -rawansi $opt_ansibase $cache_inner] - set cache_inner [punk::ansi::ansiwrap_raw $opt_ansibase "" "" $cache_inner] + #set cache_inner [punk::ansi::ansiwrap_raw $opt_ansibase "" "" $cache_inner] + #jjj ??? review + set cache_inner [punk::ansi::ansiwrap_raw $opt_ansibase "\x1b\[m" "" $cache_inner] } else { + #!!! set cache_inner "$opt_ansibase$cache_inner\x1b\[0m" } } @@ -8722,7 +8750,10 @@ tcl::namespace::eval textblock { if {$opt_ansibase ne ""} { if {$contents_has_ansi} { #set contents [punk::ansi::ansiwrap -rawansi $opt_ansibase $contents] - set contents [punk::ansi::ansiwrap_raw $opt_ansibase "" "" $contents] + #set contents [punk::ansi::ansiwrap_raw $opt_ansibase "" "" $contents] + #jjj + set contents [punk::ansi::ansiwrap_raw $opt_ansibase "\x1b\[m" "" $contents] + #set contents "$opt_ansibase$contents\x1b\[0m" } else { set contents "$opt_ansibase$contents\x1b\[0m" set contents_has_ansi 1 diff --git a/src/project_layouts/custom/_project/punk.basic/src/make.tcl b/src/project_layouts/custom/_project/punk.basic/src/make.tcl index 853185bf..2de13afb 100644 --- a/src/project_layouts/custom/_project/punk.basic/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.basic/src/make.tcl @@ -1653,7 +1653,7 @@ if {$::punkboot::command eq "bootsupport"} { file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists $boot_event targetset_started # -- --- --- --- --- --- - puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" + puts "\nBOOTSUPPORT module$which update: $srcfile -> $tgtfile" if {[catch { file copy -force $srcfile $tgtfile } errM]} { @@ -1719,7 +1719,8 @@ if {$::punkboot::command eq "bootsupport"} { set resultdict [punkcheck::install $sourcemodules $targetroot\ -overwrite installedsourcechanged-targets\ -antiglob_paths $antipaths\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] @@ -1737,7 +1738,8 @@ if {$::punkboot::command eq "bootsupport"} { -max_depth 1\ -createempty 0\ -overwrite installedsourcechanged-targets\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] flush stdout @@ -1784,7 +1786,12 @@ if {$::punkboot::command in {project modules}} { #install .tm *and other files* puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}] + set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder\ + -installer make.tcl\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths {README.md include_modules.config}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendormodulefolders]} { @@ -1810,7 +1817,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendorlibfolders]} { @@ -1906,7 +1917,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $projectlibfolders]} { @@ -1941,7 +1956,12 @@ if {$::punkboot::command in {project modules}} { set overwrite "installedsourcechanged-targets" #set overwrite "ALL-TARGETS" puts stdout "MODULEFOLDER non_tm_files $src_module_dir - copying to $target_modules_base (if source file changed)" - set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -antiglob_paths {README.md}] + set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base\ + -installer make.tcl\ + -overwrite $overwrite\ + -antiglob_paths {README.md}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm index ab1ca020..baa5daec 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm @@ -4468,13 +4468,13 @@ tcl::namespace::eval punk::args { #error "punk::args::parse Even number of -flag val pairs required after arglist" #} - #Default the -errorstyle to enhanced - # (slowest on unhappy path - but probably clearest for playing with new APIs interactively) + #Default the -errorstyle to standard + # (slow on unhappy path - but probably clearest for playing with new APIs interactively) # - application devs should distribute a config file with an errorstyle override if desired. # - devs who prefer a different default for interactive use should create a config for it. (todo) set defaultopts [dict create\ -form {*}\ - -errorstyle enhanced\ + -errorstyle standard\ ] #todo - load override_errorstyle from configuration diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm index e0bb8b65..451ad7a5 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm @@ -32,9 +32,16 @@ package require punk::mix::util #see following article regarding the many problems with using mtime for build-decisions: https://apenwarr.ca/log/20181113 # namespace eval punkcheck { + #namespace export\ + # uuid\ + # start_installer_event installfile_* namespace export\ uuid\ - start_installer_event installfile_* + installtrack\ + install\ + install_tm_files\ + install_non_tm_files\ + summarize_install_resultdict\ #antiglob_dir & antiglob_file entries match the pattern at any level - should not contain path separators variable default_antiglob_dir_core [list "#*" "_aside" "_build" ".git" ".fossil*"] @@ -48,7 +55,7 @@ namespace eval punkcheck { } if {!$has_twapi} { if {[catch {package require uuid} errM]} { - error "Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" + error "punkcheck: Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" } return [uuid::uuid generate] } else { @@ -150,6 +157,8 @@ namespace eval punkcheck { } } + #instances created by an installtrack object in method start_event + #also in installtrack constructor - to represent existing events from the .punkcheck data oo::class create installevent { variable o_id variable o_rel_sourceroot @@ -266,7 +275,7 @@ namespace eval punkcheck { set o_operation [string toupper $operation] if {$o_operation_start_ts ne ""} { - error "[self] targetset_tart $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." + error "[self] targetset_init $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." } set o_operation_start_ts [clock microseconds] set seconds [expr {$o_operation_start_ts / 1000000}] @@ -1241,7 +1250,17 @@ namespace eval punkcheck { lappend PUNKARGS [list { @id -id ::punkcheck::install @cmd -name ::punkcheck::install -help\ - "Unidirectional file transfer to possibly non-empty target folder." + "Unidirectional file transfer to possibly non-empty target folder. + This is the simpler form of the API, performing a transfer from one + directory tree to another, copying each file when changes in the source + file are detected. + Changes are detected by content checksum. The first install will record + source checksums in a .punkcheck file (ideally located at the root of the + target folder). Subsequent installs will compare stored checksums with + the current checksums of the source files. + For more advanced install operations, the object command installtrack + can be used to define install operations. e.g when the transfer is not + one-to-one and a target file depends on multiple source files." @leaders -min 2 -max 2 srcdir -type directory tgtdir -type directory @@ -1251,7 +1270,7 @@ namespace eval punkcheck { "Deepest subdirectory - use -1 for no limit." -createdir -type boolean -default 0 -help\ "Whether to create the folder at tgtdir. - Any required subdirectories are created regardless of this setting." + Any required subdirectories are created regardless of this setting." -createempty -type boolean -default 0 -help\ "Whether to create folders at target that had no matches for our glob" -glob -type string -default "*" -help\ @@ -1282,8 +1301,8 @@ namespace eval punkcheck { } -punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\ "The location of the .punkcheck file to track installations and checksums. - The default value 'target' is generally recommended. - Can also be an absolute path to a folder." + The default value 'target' is generally recommended. + Can also be an absolute path to a folder." -punkcheck_records -default "" -help\ "Empty string or a parsed TDL records structure. e.g @@ -1294,7 +1313,14 @@ namespace eval punkcheck { }" -installer -default "punkcheck::install" -help\ "A user nominated string that is stored in the .punkcheck file - This might be the name of a script or installation process." + This might be the name of a script or installation process." + -progresschannel -default none -type string -help\ + "Name of channel e.g stderr, stdout to which progress messages are written. + This includes the tree-like output consisting of dots (or green U) for each + file processed. As the number of files in a tree is not known beforehand, + it isn't useful for a percentage-based progress meter, but it could potentially + be used to drive a spinner if the textual data is not desired. + Setting to none or an invalid channel will deactivate the output." }] ## unidirectional file transfer to possibly non empty folder #default of -overwrite no-targets will only copy files that are missing at the target @@ -1343,6 +1369,7 @@ namespace eval punkcheck { -punkcheck_eventid "\uFFFF"\ -punkcheck_records ""\ -installer punkcheck::install\ + -progresschannel none\ ] if {([llength $args] %2) != 0} { @@ -1367,6 +1394,10 @@ namespace eval punkcheck { set fileglob [dict get $opts -glob] set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting set opt_createempty [dict get $opts -createempty] + set opt_progresschannel [dict get $opts -progresschannel] + if {$opt_progresschannel in {"" none} || [catch {chan configure $opt_progresschannel}]} { + set opt_progresschannel "" + } if {$CALLDEPTH == 0} { #expensive to normalize but we need to do it at least once @@ -1485,6 +1516,7 @@ namespace eval punkcheck { dict unset config -call-depth-internal dict unset config -max_depth dict unset config -subdirlist + dict unset config -progresschannel tcl::dict::for {k v} $config { if {$v eq "\uFFFF"} { dict unset config $k @@ -1602,7 +1634,7 @@ namespace eval punkcheck { #puts stdout "Current target dir: $current_target_dir" - set last_processed_dir "" + set last_depth "" foreach m $match_list { set new_tgt_cksum_info [list] set relative_target_path [file join $relative_target_dir $m] @@ -1620,23 +1652,6 @@ namespace eval punkcheck { if {$is_antipath} { continue } - #puts stdout " checking file : $current_source_dir/$m" - #give some output - but not too deep - #set thismatchdir [file dirname [file dirname [file join $srcdir $relative_source_dir]]] - #if {$last_processed_dir ne $thismatchdir} { - # puts stdout "\n checking files in $thismatchdir" - # set last_processed_dir $thismatchdir - #} else { - # puts -nonewline stdout . - #} - if {$last_processed_dir ne $CALLDEPTH} { - puts -nonewline stdout \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] - puts -nonewline stdout \n[string repeat " " $CALLDEPTH]. - flush stdout - set last_processed_dir $CALLDEPTH - } else { - puts -nonewline stdout . - } set ts_start [clock microseconds] set seconds [expr {$ts_start / 1000000}] @@ -1649,7 +1664,9 @@ namespace eval punkcheck { #change to use extract_or_create_fileset_record ? set existing_filerec_posn [dict get $fetch_filerec_result position] if {$existing_filerec_posn == -1} { - puts stdout "NO existing record for $punkcheck_target_relpath" + if {$opt_progresschannel ne ""} { + puts stdout "\nNO existing record for $punkcheck_target_relpath" + } set has_filerec 0 set new_filerec [dict create tag FILEINFO -targets $punkcheck_target_relpath] set filerec $new_filerec @@ -1667,14 +1684,24 @@ namespace eval punkcheck { unset new_install_record + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + if {$CALLDEPTH <=1} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] + } + flush $opt_progresschannel + ##set last_depth $CALLDEPTH ;# done down below + } + } set relative_source_path [lib::path_relative $punkcheck_folder $current_source_dir/$m] #puts stdout " rel_source: $relative_source_path" - if {[file pathtype $relative_source_path] ne "relative"} { + #if {[file pathtype $relative_source_path] ne "relative"} { + #REVIEW #different volume or root - } + #} #Note this isn't a recordlist function - so it doesn't purely operate on the records #this hits the filesystem for the sourcepath - gets checksums/timestamps depending on config. #It doesn't save to .punkcheck (the only punkcheck::installfile_ method which doesn't) @@ -1683,18 +1710,19 @@ namespace eval punkcheck { set ts2 [clock milliseconds] set diff [expr {$ts2 - $ts1}] if {$diff > 100} { + #todo -errorchannel set errprefix ">>> punkcheck:" - puts stderr "$errprefix performance warning: fetch_metadata for $m took $diff ms." + puts stderr "\n$errprefix performance warning: fetch_metadata for $m took $diff ms." set lb [lindex [dict get $filerec body] end] #puts stderr "$errprefix filerec last body record:$lb" set records [dict get $lb body] set lr [lindex $records end] set alg [dict get $lr -cksum_all_opts -cksum_algorithm] if {$alg eq "sha1"} { - puts "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" - puts "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" + puts stderr "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" + puts stderr "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" } else { - puts "$errprefix cksum_algorithm: $alg" + puts stderr "$errprefix cksum_algorithm: $alg" } } @@ -1710,6 +1738,7 @@ namespace eval punkcheck { } set is_skip 0 + set is_new 0 if {$overwrite_what eq "all-targets"} { file mkdir $current_target_dir #-------------------------------------------- @@ -1720,12 +1749,13 @@ namespace eval punkcheck { lappend files_copied $current_source_dir/$m } else { if {![file exists $current_target_dir/$m]} { - puts stderr "punkcheck: first copy to $current_target_dir/$m " + #puts stderr "punkcheck: first copy to $current_target_dir/$m " file mkdir $current_target_dir file copy $current_source_dir/$m $current_target_dir set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] lappend files_copied $current_source_dir/$m incr filecount_new + set is_new 1 } else { switch -- $overwrite_what { installedsourcechanged-targets { @@ -1747,14 +1777,16 @@ namespace eval punkcheck { } } synced-targets { + #disallow overwriting of target that has been modified by some other mechanism + #review if {[llength $changed]} { - #only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized) + #only overwrite if the target file checksum equals the last installed checksum (ie target is in sync with previous source-set and so hasn't been customized) set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] set is_target_unmodified_since_install 0 set target_cksum_compare "unknown" set latest_install_record [punkcheck::recordlist::file_record_latest_installrecord $filerec] ;#may be no such record - in which case we get an empty list if {[dict exists $latest_install_record -targets_cksums]} { - set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) + set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) if {[dict get $existing_tgt_cksum_info cksum] eq $last_install_cksum} { set is_target_unmodified_since_install 1 set target_cksum_compare "match" @@ -1797,6 +1829,7 @@ namespace eval punkcheck { + set ts_now [clock microseconds] set elapsed_us [expr {$ts_now - $ts_start}] @@ -1827,6 +1860,29 @@ namespace eval punkcheck { lset punkcheck_records $existing_filerec_posn $filerec } + + #------------------------------------------------------------ + if {$is_skip} { + set mark . + } else { + if {$is_new} { + set mark \x1b\[32\;1mN\x1b\[m + } else { + #updated + set mark \x1b\[32\;1mU\x1b\[m + } + } + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH]$mark + flush $opt_progresschannel + set last_depth $CALLDEPTH + } else { + puts -nonewline $opt_progresschannel $mark + } + } + #------------------------------------------------------------ + } if {$max_depth != -1 && $CALLDEPTH >= $max_depth} { @@ -1905,6 +1961,7 @@ namespace eval punkcheck { -punkcheck_folder $punkcheck_folder\ -punkcheck_eventid $punkcheck_eventid\ -punkcheck_records $punkcheck_records\ + -progresschannel $opt_progresschannel\ ] set sub_opts [dict merge $opts $sub_opts] set sub_result [punkcheck::install $srcdir $tgtdir {*}$sub_opts] 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 c89b3594..65c5aaa7 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 @@ -60,6 +60,7 @@ package require punk::ansi package require punk::lib catch {package require patternpunk} package require overtype +package require struct::set #safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list. #2025 - required term::ansi features for altg now built in to textblock diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl index 853185bf..2de13afb 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl @@ -1653,7 +1653,7 @@ if {$::punkboot::command eq "bootsupport"} { file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists $boot_event targetset_started # -- --- --- --- --- --- - puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" + puts "\nBOOTSUPPORT module$which update: $srcfile -> $tgtfile" if {[catch { file copy -force $srcfile $tgtfile } errM]} { @@ -1719,7 +1719,8 @@ if {$::punkboot::command eq "bootsupport"} { set resultdict [punkcheck::install $sourcemodules $targetroot\ -overwrite installedsourcechanged-targets\ -antiglob_paths $antipaths\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] @@ -1737,7 +1738,8 @@ if {$::punkboot::command eq "bootsupport"} { -max_depth 1\ -createempty 0\ -overwrite installedsourcechanged-targets\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] flush stdout @@ -1784,7 +1786,12 @@ if {$::punkboot::command in {project modules}} { #install .tm *and other files* puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}] + set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder\ + -installer make.tcl\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths {README.md include_modules.config}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendormodulefolders]} { @@ -1810,7 +1817,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendorlibfolders]} { @@ -1906,7 +1917,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $projectlibfolders]} { @@ -1941,7 +1956,12 @@ if {$::punkboot::command in {project modules}} { set overwrite "installedsourcechanged-targets" #set overwrite "ALL-TARGETS" puts stdout "MODULEFOLDER non_tm_files $src_module_dir - copying to $target_modules_base (if source file changed)" - set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -antiglob_paths {README.md}] + set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base\ + -installer make.tcl\ + -overwrite $overwrite\ + -antiglob_paths {README.md}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm index ab1ca020..baa5daec 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm @@ -4468,13 +4468,13 @@ tcl::namespace::eval punk::args { #error "punk::args::parse Even number of -flag val pairs required after arglist" #} - #Default the -errorstyle to enhanced - # (slowest on unhappy path - but probably clearest for playing with new APIs interactively) + #Default the -errorstyle to standard + # (slow on unhappy path - but probably clearest for playing with new APIs interactively) # - application devs should distribute a config file with an errorstyle override if desired. # - devs who prefer a different default for interactive use should create a config for it. (todo) set defaultopts [dict create\ -form {*}\ - -errorstyle enhanced\ + -errorstyle standard\ ] #todo - load override_errorstyle from configuration diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm index e0bb8b65..451ad7a5 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm @@ -32,9 +32,16 @@ package require punk::mix::util #see following article regarding the many problems with using mtime for build-decisions: https://apenwarr.ca/log/20181113 # namespace eval punkcheck { + #namespace export\ + # uuid\ + # start_installer_event installfile_* namespace export\ uuid\ - start_installer_event installfile_* + installtrack\ + install\ + install_tm_files\ + install_non_tm_files\ + summarize_install_resultdict\ #antiglob_dir & antiglob_file entries match the pattern at any level - should not contain path separators variable default_antiglob_dir_core [list "#*" "_aside" "_build" ".git" ".fossil*"] @@ -48,7 +55,7 @@ namespace eval punkcheck { } if {!$has_twapi} { if {[catch {package require uuid} errM]} { - error "Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" + error "punkcheck: Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" } return [uuid::uuid generate] } else { @@ -150,6 +157,8 @@ namespace eval punkcheck { } } + #instances created by an installtrack object in method start_event + #also in installtrack constructor - to represent existing events from the .punkcheck data oo::class create installevent { variable o_id variable o_rel_sourceroot @@ -266,7 +275,7 @@ namespace eval punkcheck { set o_operation [string toupper $operation] if {$o_operation_start_ts ne ""} { - error "[self] targetset_tart $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." + error "[self] targetset_init $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." } set o_operation_start_ts [clock microseconds] set seconds [expr {$o_operation_start_ts / 1000000}] @@ -1241,7 +1250,17 @@ namespace eval punkcheck { lappend PUNKARGS [list { @id -id ::punkcheck::install @cmd -name ::punkcheck::install -help\ - "Unidirectional file transfer to possibly non-empty target folder." + "Unidirectional file transfer to possibly non-empty target folder. + This is the simpler form of the API, performing a transfer from one + directory tree to another, copying each file when changes in the source + file are detected. + Changes are detected by content checksum. The first install will record + source checksums in a .punkcheck file (ideally located at the root of the + target folder). Subsequent installs will compare stored checksums with + the current checksums of the source files. + For more advanced install operations, the object command installtrack + can be used to define install operations. e.g when the transfer is not + one-to-one and a target file depends on multiple source files." @leaders -min 2 -max 2 srcdir -type directory tgtdir -type directory @@ -1251,7 +1270,7 @@ namespace eval punkcheck { "Deepest subdirectory - use -1 for no limit." -createdir -type boolean -default 0 -help\ "Whether to create the folder at tgtdir. - Any required subdirectories are created regardless of this setting." + Any required subdirectories are created regardless of this setting." -createempty -type boolean -default 0 -help\ "Whether to create folders at target that had no matches for our glob" -glob -type string -default "*" -help\ @@ -1282,8 +1301,8 @@ namespace eval punkcheck { } -punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\ "The location of the .punkcheck file to track installations and checksums. - The default value 'target' is generally recommended. - Can also be an absolute path to a folder." + The default value 'target' is generally recommended. + Can also be an absolute path to a folder." -punkcheck_records -default "" -help\ "Empty string or a parsed TDL records structure. e.g @@ -1294,7 +1313,14 @@ namespace eval punkcheck { }" -installer -default "punkcheck::install" -help\ "A user nominated string that is stored in the .punkcheck file - This might be the name of a script or installation process." + This might be the name of a script or installation process." + -progresschannel -default none -type string -help\ + "Name of channel e.g stderr, stdout to which progress messages are written. + This includes the tree-like output consisting of dots (or green U) for each + file processed. As the number of files in a tree is not known beforehand, + it isn't useful for a percentage-based progress meter, but it could potentially + be used to drive a spinner if the textual data is not desired. + Setting to none or an invalid channel will deactivate the output." }] ## unidirectional file transfer to possibly non empty folder #default of -overwrite no-targets will only copy files that are missing at the target @@ -1343,6 +1369,7 @@ namespace eval punkcheck { -punkcheck_eventid "\uFFFF"\ -punkcheck_records ""\ -installer punkcheck::install\ + -progresschannel none\ ] if {([llength $args] %2) != 0} { @@ -1367,6 +1394,10 @@ namespace eval punkcheck { set fileglob [dict get $opts -glob] set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting set opt_createempty [dict get $opts -createempty] + set opt_progresschannel [dict get $opts -progresschannel] + if {$opt_progresschannel in {"" none} || [catch {chan configure $opt_progresschannel}]} { + set opt_progresschannel "" + } if {$CALLDEPTH == 0} { #expensive to normalize but we need to do it at least once @@ -1485,6 +1516,7 @@ namespace eval punkcheck { dict unset config -call-depth-internal dict unset config -max_depth dict unset config -subdirlist + dict unset config -progresschannel tcl::dict::for {k v} $config { if {$v eq "\uFFFF"} { dict unset config $k @@ -1602,7 +1634,7 @@ namespace eval punkcheck { #puts stdout "Current target dir: $current_target_dir" - set last_processed_dir "" + set last_depth "" foreach m $match_list { set new_tgt_cksum_info [list] set relative_target_path [file join $relative_target_dir $m] @@ -1620,23 +1652,6 @@ namespace eval punkcheck { if {$is_antipath} { continue } - #puts stdout " checking file : $current_source_dir/$m" - #give some output - but not too deep - #set thismatchdir [file dirname [file dirname [file join $srcdir $relative_source_dir]]] - #if {$last_processed_dir ne $thismatchdir} { - # puts stdout "\n checking files in $thismatchdir" - # set last_processed_dir $thismatchdir - #} else { - # puts -nonewline stdout . - #} - if {$last_processed_dir ne $CALLDEPTH} { - puts -nonewline stdout \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] - puts -nonewline stdout \n[string repeat " " $CALLDEPTH]. - flush stdout - set last_processed_dir $CALLDEPTH - } else { - puts -nonewline stdout . - } set ts_start [clock microseconds] set seconds [expr {$ts_start / 1000000}] @@ -1649,7 +1664,9 @@ namespace eval punkcheck { #change to use extract_or_create_fileset_record ? set existing_filerec_posn [dict get $fetch_filerec_result position] if {$existing_filerec_posn == -1} { - puts stdout "NO existing record for $punkcheck_target_relpath" + if {$opt_progresschannel ne ""} { + puts stdout "\nNO existing record for $punkcheck_target_relpath" + } set has_filerec 0 set new_filerec [dict create tag FILEINFO -targets $punkcheck_target_relpath] set filerec $new_filerec @@ -1667,14 +1684,24 @@ namespace eval punkcheck { unset new_install_record + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + if {$CALLDEPTH <=1} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] + } + flush $opt_progresschannel + ##set last_depth $CALLDEPTH ;# done down below + } + } set relative_source_path [lib::path_relative $punkcheck_folder $current_source_dir/$m] #puts stdout " rel_source: $relative_source_path" - if {[file pathtype $relative_source_path] ne "relative"} { + #if {[file pathtype $relative_source_path] ne "relative"} { + #REVIEW #different volume or root - } + #} #Note this isn't a recordlist function - so it doesn't purely operate on the records #this hits the filesystem for the sourcepath - gets checksums/timestamps depending on config. #It doesn't save to .punkcheck (the only punkcheck::installfile_ method which doesn't) @@ -1683,18 +1710,19 @@ namespace eval punkcheck { set ts2 [clock milliseconds] set diff [expr {$ts2 - $ts1}] if {$diff > 100} { + #todo -errorchannel set errprefix ">>> punkcheck:" - puts stderr "$errprefix performance warning: fetch_metadata for $m took $diff ms." + puts stderr "\n$errprefix performance warning: fetch_metadata for $m took $diff ms." set lb [lindex [dict get $filerec body] end] #puts stderr "$errprefix filerec last body record:$lb" set records [dict get $lb body] set lr [lindex $records end] set alg [dict get $lr -cksum_all_opts -cksum_algorithm] if {$alg eq "sha1"} { - puts "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" - puts "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" + puts stderr "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" + puts stderr "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" } else { - puts "$errprefix cksum_algorithm: $alg" + puts stderr "$errprefix cksum_algorithm: $alg" } } @@ -1710,6 +1738,7 @@ namespace eval punkcheck { } set is_skip 0 + set is_new 0 if {$overwrite_what eq "all-targets"} { file mkdir $current_target_dir #-------------------------------------------- @@ -1720,12 +1749,13 @@ namespace eval punkcheck { lappend files_copied $current_source_dir/$m } else { if {![file exists $current_target_dir/$m]} { - puts stderr "punkcheck: first copy to $current_target_dir/$m " + #puts stderr "punkcheck: first copy to $current_target_dir/$m " file mkdir $current_target_dir file copy $current_source_dir/$m $current_target_dir set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] lappend files_copied $current_source_dir/$m incr filecount_new + set is_new 1 } else { switch -- $overwrite_what { installedsourcechanged-targets { @@ -1747,14 +1777,16 @@ namespace eval punkcheck { } } synced-targets { + #disallow overwriting of target that has been modified by some other mechanism + #review if {[llength $changed]} { - #only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized) + #only overwrite if the target file checksum equals the last installed checksum (ie target is in sync with previous source-set and so hasn't been customized) set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] set is_target_unmodified_since_install 0 set target_cksum_compare "unknown" set latest_install_record [punkcheck::recordlist::file_record_latest_installrecord $filerec] ;#may be no such record - in which case we get an empty list if {[dict exists $latest_install_record -targets_cksums]} { - set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) + set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) if {[dict get $existing_tgt_cksum_info cksum] eq $last_install_cksum} { set is_target_unmodified_since_install 1 set target_cksum_compare "match" @@ -1797,6 +1829,7 @@ namespace eval punkcheck { + set ts_now [clock microseconds] set elapsed_us [expr {$ts_now - $ts_start}] @@ -1827,6 +1860,29 @@ namespace eval punkcheck { lset punkcheck_records $existing_filerec_posn $filerec } + + #------------------------------------------------------------ + if {$is_skip} { + set mark . + } else { + if {$is_new} { + set mark \x1b\[32\;1mN\x1b\[m + } else { + #updated + set mark \x1b\[32\;1mU\x1b\[m + } + } + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH]$mark + flush $opt_progresschannel + set last_depth $CALLDEPTH + } else { + puts -nonewline $opt_progresschannel $mark + } + } + #------------------------------------------------------------ + } if {$max_depth != -1 && $CALLDEPTH >= $max_depth} { @@ -1905,6 +1961,7 @@ namespace eval punkcheck { -punkcheck_folder $punkcheck_folder\ -punkcheck_eventid $punkcheck_eventid\ -punkcheck_records $punkcheck_records\ + -progresschannel $opt_progresschannel\ ] set sub_opts [dict merge $opts $sub_opts] set sub_result [punkcheck::install $srcdir $tgtdir {*}$sub_opts] 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 c89b3594..65c5aaa7 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 @@ -60,6 +60,7 @@ package require punk::ansi package require punk::lib catch {package require patternpunk} package require overtype +package require struct::set #safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list. #2025 - required term::ansi features for altg now built in to textblock diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl index 853185bf..2de13afb 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl @@ -1653,7 +1653,7 @@ if {$::punkboot::command eq "bootsupport"} { file mkdir [file dirname $tgtfile] ;#ensure containing folder for target exists $boot_event targetset_started # -- --- --- --- --- --- - puts "BOOTSUPPORT module$which update: $srcfile -> $tgtfile" + puts "\nBOOTSUPPORT module$which update: $srcfile -> $tgtfile" if {[catch { file copy -force $srcfile $tgtfile } errM]} { @@ -1719,7 +1719,8 @@ if {$::punkboot::command eq "bootsupport"} { set resultdict [punkcheck::install $sourcemodules $targetroot\ -overwrite installedsourcechanged-targets\ -antiglob_paths $antipaths\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] @@ -1737,7 +1738,8 @@ if {$::punkboot::command eq "bootsupport"} { -max_depth 1\ -createempty 0\ -overwrite installedsourcechanged-targets\ - -installer "punkboot-bootsupport" + -installer "punkboot-bootsupport"\ + -progresschannel stdout\ ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] flush stdout @@ -1784,7 +1786,12 @@ if {$::punkboot::command in {project modules}} { #install .tm *and other files* puts stdout "VENDORMODULES$which: copying from $sourcefolder/$vf to $target_module_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder -installer make.tcl -overwrite installedsourcechanged-targets -antiglob_paths {README.md include_modules.config}] + set resultdict [punkcheck::install $sourcefolder/$vf $target_module_folder\ + -installer make.tcl\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths {README.md include_modules.config}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendormodulefolders]} { @@ -1810,7 +1817,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "VENDORLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $vendorlibfolders]} { @@ -1906,7 +1917,11 @@ if {$::punkboot::command in {project modules}} { README.md\ ] puts stdout "PROJECTLIB$which: copying from $sourcefolder/$lf to $target_lib_folder (if source file changed)" - set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder -overwrite installedsourcechanged-targets -antiglob_paths $antipaths] + set resultdict [punkcheck::install $sourcefolder/$lf $target_lib_folder\ + -overwrite installedsourcechanged-targets\ + -antiglob_paths $antipaths\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } if {![llength $projectlibfolders]} { @@ -1941,7 +1956,12 @@ if {$::punkboot::command in {project modules}} { set overwrite "installedsourcechanged-targets" #set overwrite "ALL-TARGETS" puts stdout "MODULEFOLDER non_tm_files $src_module_dir - copying to $target_modules_base (if source file changed)" - set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -antiglob_paths {README.md}] + set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base\ + -installer make.tcl\ + -overwrite $overwrite\ + -antiglob_paths {README.md}\ + -progresschannel stdout\ + ] puts stdout [punkcheck::summarize_install_resultdict $resultdict] } diff --git a/src/vfs/_vfscommon.vfs/modules/punk/args-0.2.tm b/src/vfs/_vfscommon.vfs/modules/punk/args-0.2.tm index ab1ca020..baa5daec 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/args-0.2.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/args-0.2.tm @@ -4468,13 +4468,13 @@ tcl::namespace::eval punk::args { #error "punk::args::parse Even number of -flag val pairs required after arglist" #} - #Default the -errorstyle to enhanced - # (slowest on unhappy path - but probably clearest for playing with new APIs interactively) + #Default the -errorstyle to standard + # (slow on unhappy path - but probably clearest for playing with new APIs interactively) # - application devs should distribute a config file with an errorstyle override if desired. # - devs who prefer a different default for interactive use should create a config for it. (todo) set defaultopts [dict create\ -form {*}\ - -errorstyle enhanced\ + -errorstyle standard\ ] #todo - load override_errorstyle from configuration diff --git a/src/vfs/_vfscommon.vfs/modules/punkcheck-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punkcheck-0.1.0.tm index 199d06bf..451ad7a5 100644 --- a/src/vfs/_vfscommon.vfs/modules/punkcheck-0.1.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punkcheck-0.1.0.tm @@ -32,9 +32,16 @@ package require punk::mix::util #see following article regarding the many problems with using mtime for build-decisions: https://apenwarr.ca/log/20181113 # namespace eval punkcheck { + #namespace export\ + # uuid\ + # start_installer_event installfile_* namespace export\ uuid\ - start_installer_event installfile_* + installtrack\ + install\ + install_tm_files\ + install_non_tm_files\ + summarize_install_resultdict\ #antiglob_dir & antiglob_file entries match the pattern at any level - should not contain path separators variable default_antiglob_dir_core [list "#*" "_aside" "_build" ".git" ".fossil*"] @@ -48,7 +55,7 @@ namespace eval punkcheck { } if {!$has_twapi} { if {[catch {package require uuid} errM]} { - error "Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" + error "punkcheck: Unable to load a package for uuid on this platform. Try tcllib's uuid (any platform) - twapi for windows" } return [uuid::uuid generate] } else { @@ -150,6 +157,8 @@ namespace eval punkcheck { } } + #instances created by an installtrack object in method start_event + #also in installtrack constructor - to represent existing events from the .punkcheck data oo::class create installevent { variable o_id variable o_rel_sourceroot @@ -266,7 +275,7 @@ namespace eval punkcheck { set o_operation [string toupper $operation] if {$o_operation_start_ts ne ""} { - error "[self] targetset_tart $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." + error "[self] targetset_init $o_operation operation already in progress. Use targetset_finished or targetset_complete to finish." } set o_operation_start_ts [clock microseconds] set seconds [expr {$o_operation_start_ts / 1000000}] @@ -1241,7 +1250,17 @@ namespace eval punkcheck { lappend PUNKARGS [list { @id -id ::punkcheck::install @cmd -name ::punkcheck::install -help\ - "Unidirectional file transfer to possibly non-empty target folder." + "Unidirectional file transfer to possibly non-empty target folder. + This is the simpler form of the API, performing a transfer from one + directory tree to another, copying each file when changes in the source + file are detected. + Changes are detected by content checksum. The first install will record + source checksums in a .punkcheck file (ideally located at the root of the + target folder). Subsequent installs will compare stored checksums with + the current checksums of the source files. + For more advanced install operations, the object command installtrack + can be used to define install operations. e.g when the transfer is not + one-to-one and a target file depends on multiple source files." @leaders -min 2 -max 2 srcdir -type directory tgtdir -type directory @@ -1251,7 +1270,7 @@ namespace eval punkcheck { "Deepest subdirectory - use -1 for no limit." -createdir -type boolean -default 0 -help\ "Whether to create the folder at tgtdir. - Any required subdirectories are created regardless of this setting." + Any required subdirectories are created regardless of this setting." -createempty -type boolean -default 0 -help\ "Whether to create folders at target that had no matches for our glob" -glob -type string -default "*" -help\ @@ -1282,8 +1301,8 @@ namespace eval punkcheck { } -punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\ "The location of the .punkcheck file to track installations and checksums. - The default value 'target' is generally recommended. - Can also be an absolute path to a folder." + The default value 'target' is generally recommended. + Can also be an absolute path to a folder." -punkcheck_records -default "" -help\ "Empty string or a parsed TDL records structure. e.g @@ -1294,7 +1313,14 @@ namespace eval punkcheck { }" -installer -default "punkcheck::install" -help\ "A user nominated string that is stored in the .punkcheck file - This might be the name of a script or installation process." + This might be the name of a script or installation process." + -progresschannel -default none -type string -help\ + "Name of channel e.g stderr, stdout to which progress messages are written. + This includes the tree-like output consisting of dots (or green U) for each + file processed. As the number of files in a tree is not known beforehand, + it isn't useful for a percentage-based progress meter, but it could potentially + be used to drive a spinner if the textual data is not desired. + Setting to none or an invalid channel will deactivate the output." }] ## unidirectional file transfer to possibly non empty folder #default of -overwrite no-targets will only copy files that are missing at the target @@ -1343,6 +1369,7 @@ namespace eval punkcheck { -punkcheck_eventid "\uFFFF"\ -punkcheck_records ""\ -installer punkcheck::install\ + -progresschannel none\ ] if {([llength $args] %2) != 0} { @@ -1367,6 +1394,10 @@ namespace eval punkcheck { set fileglob [dict get $opts -glob] set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting set opt_createempty [dict get $opts -createempty] + set opt_progresschannel [dict get $opts -progresschannel] + if {$opt_progresschannel in {"" none} || [catch {chan configure $opt_progresschannel}]} { + set opt_progresschannel "" + } if {$CALLDEPTH == 0} { #expensive to normalize but we need to do it at least once @@ -1485,6 +1516,7 @@ namespace eval punkcheck { dict unset config -call-depth-internal dict unset config -max_depth dict unset config -subdirlist + dict unset config -progresschannel tcl::dict::for {k v} $config { if {$v eq "\uFFFF"} { dict unset config $k @@ -1602,7 +1634,7 @@ namespace eval punkcheck { #puts stdout "Current target dir: $current_target_dir" - set last_processed_dir "" + set last_depth "" foreach m $match_list { set new_tgt_cksum_info [list] set relative_target_path [file join $relative_target_dir $m] @@ -1620,14 +1652,7 @@ namespace eval punkcheck { if {$is_antipath} { continue } - #puts stdout " checking file : $current_source_dir/$m" - set thismatchdir [file dirname $m] - if {$last_processed_dir ne $thismatchdir} { - puts stdout "\n checking files in [file join $current_source_dir $thismatchdir]" - set last_processed_dir $thismatchdir - } else { - puts -nonewline stdout . - } + set ts_start [clock microseconds] set seconds [expr {$ts_start / 1000000}] set ts_start_iso [clock format $seconds -format "%Y-%m-%dT%H:%M:%S"] @@ -1639,7 +1664,9 @@ namespace eval punkcheck { #change to use extract_or_create_fileset_record ? set existing_filerec_posn [dict get $fetch_filerec_result position] if {$existing_filerec_posn == -1} { - puts stdout "NO existing record for $punkcheck_target_relpath" + if {$opt_progresschannel ne ""} { + puts stdout "\nNO existing record for $punkcheck_target_relpath" + } set has_filerec 0 set new_filerec [dict create tag FILEINFO -targets $punkcheck_target_relpath] set filerec $new_filerec @@ -1657,14 +1684,24 @@ namespace eval punkcheck { unset new_install_record + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + if {$CALLDEPTH <=1} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH][file tail $relative_source_dir] + } + flush $opt_progresschannel + ##set last_depth $CALLDEPTH ;# done down below + } + } set relative_source_path [lib::path_relative $punkcheck_folder $current_source_dir/$m] #puts stdout " rel_source: $relative_source_path" - if {[file pathtype $relative_source_path] ne "relative"} { + #if {[file pathtype $relative_source_path] ne "relative"} { + #REVIEW #different volume or root - } + #} #Note this isn't a recordlist function - so it doesn't purely operate on the records #this hits the filesystem for the sourcepath - gets checksums/timestamps depending on config. #It doesn't save to .punkcheck (the only punkcheck::installfile_ method which doesn't) @@ -1673,18 +1710,19 @@ namespace eval punkcheck { set ts2 [clock milliseconds] set diff [expr {$ts2 - $ts1}] if {$diff > 100} { + #todo -errorchannel set errprefix ">>> punkcheck:" - puts stderr "$errprefix performance warning: fetch_metadata for $m took $diff ms." + puts stderr "\n$errprefix performance warning: fetch_metadata for $m took $diff ms." set lb [lindex [dict get $filerec body] end] #puts stderr "$errprefix filerec last body record:$lb" set records [dict get $lb body] set lr [lindex $records end] set alg [dict get $lr -cksum_all_opts -cksum_algorithm] if {$alg eq "sha1"} { - puts "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" - puts "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" + puts stderr "$errprefix cksum_algorithm: sha1 (accelerators: [::sha1::Implementations])" + puts stderr "$errprefix sha1 from: [package ifneeded sha1 [package present sha1]]" } else { - puts "$errprefix cksum_algorithm: $alg" + puts stderr "$errprefix cksum_algorithm: $alg" } } @@ -1700,6 +1738,7 @@ namespace eval punkcheck { } set is_skip 0 + set is_new 0 if {$overwrite_what eq "all-targets"} { file mkdir $current_target_dir #-------------------------------------------- @@ -1710,12 +1749,13 @@ namespace eval punkcheck { lappend files_copied $current_source_dir/$m } else { if {![file exists $current_target_dir/$m]} { - puts stderr "punkcheck: first copy to $current_target_dir/$m " + #puts stderr "punkcheck: first copy to $current_target_dir/$m " file mkdir $current_target_dir file copy $current_source_dir/$m $current_target_dir set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] lappend files_copied $current_source_dir/$m incr filecount_new + set is_new 1 } else { switch -- $overwrite_what { installedsourcechanged-targets { @@ -1737,14 +1777,16 @@ namespace eval punkcheck { } } synced-targets { + #disallow overwriting of target that has been modified by some other mechanism + #review if {[llength $changed]} { - #only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized) + #only overwrite if the target file checksum equals the last installed checksum (ie target is in sync with previous source-set and so hasn't been customized) set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] set is_target_unmodified_since_install 0 set target_cksum_compare "unknown" set latest_install_record [punkcheck::recordlist::file_record_latest_installrecord $filerec] ;#may be no such record - in which case we get an empty list if {[dict exists $latest_install_record -targets_cksums]} { - set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) + set last_install_cksum [dict get $latest_install_record -targets_cksums] ;#in this case we know there is only one as 'install' always uses targetset size of 1. (FILEINFO record per file in source folder) if {[dict get $existing_tgt_cksum_info cksum] eq $last_install_cksum} { set is_target_unmodified_since_install 1 set target_cksum_compare "match" @@ -1787,6 +1829,7 @@ namespace eval punkcheck { + set ts_now [clock microseconds] set elapsed_us [expr {$ts_now - $ts_start}] @@ -1817,10 +1860,35 @@ namespace eval punkcheck { lset punkcheck_records $existing_filerec_posn $filerec } + + #------------------------------------------------------------ + if {$is_skip} { + set mark . + } else { + if {$is_new} { + set mark \x1b\[32\;1mN\x1b\[m + } else { + #updated + set mark \x1b\[32\;1mU\x1b\[m + } + } + if {$opt_progresschannel ne ""} { + if {$last_depth ne $CALLDEPTH} { + puts -nonewline $opt_progresschannel \n[string repeat " " $CALLDEPTH]$mark + flush $opt_progresschannel + set last_depth $CALLDEPTH + } else { + puts -nonewline $opt_progresschannel $mark + } + } + #------------------------------------------------------------ + } if {$max_depth != -1 && $CALLDEPTH >= $max_depth} { #don't process any more subdirs + #sometimes deliberately called with max_depth 1 - so don't warn here. review + #puts stderr "punkcheck::install warning - reached max_depth $max_depth" set subdirs [list] } else { set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *] @@ -1893,6 +1961,7 @@ namespace eval punkcheck { -punkcheck_folder $punkcheck_folder\ -punkcheck_eventid $punkcheck_eventid\ -punkcheck_records $punkcheck_records\ + -progresschannel $opt_progresschannel\ ] set sub_opts [dict merge $opts $sub_opts] set sub_result [punkcheck::install $srcdir $tgtdir {*}$sub_opts] @@ -1910,7 +1979,7 @@ namespace eval punkcheck { if {[llength $files_copied] || [llength $files_skipped]} { #puts stdout ">>>>>>>>>>>>>>>>>>>" set saveresult [punkcheck::save_records_to_file $punkcheck_records $punkcheck_file] - puts stdout "punkcheck::install [dict get $saveresult recordcount] records saved as [dict get $saveresult linecount] lines to $punkcheck_file copied: [llength $files_copied] skipped: [llength $files_skipped]" + puts stdout "\npunkcheck::install [dict get $saveresult recordcount] records saved as [dict get $saveresult linecount] lines to $punkcheck_file copied: [llength $files_copied] skipped: [llength $files_skipped]" #puts stdout ">>>>>>>>>>>>>>>>>>>" } else { #todo - write db INSTALLER record if -debug true 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 c89b3594..65c5aaa7 100644 --- a/src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm +++ b/src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm @@ -60,6 +60,7 @@ package require punk::ansi package require punk::lib catch {package require patternpunk} package require overtype +package require struct::set #safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list. #2025 - required term::ansi features for altg now built in to textblock