diff --git a/.fossil-custom/mainmenu b/.fossil-custom/mainmenu new file mode 100644 index 0000000..8bee6e7 --- /dev/null +++ b/.fossil-custom/mainmenu @@ -0,0 +1,13 @@ +Home /home * {} +Timeline /timeline {o r j} {} +Files /dir?ci=tip oh desktoponly +Branches /brlist o wideonly +Tags /taglist o wideonly +Forum /forum {@2 3 4 5 6} wideonly +Chat /chat C wideonly +Tickets /ticket r wideonly +Wiki /wiki j wideonly +Download /download * {} +Admin /setup {a s} desktoponly +Logout /logout L wideonly +Login /login !L wideonly diff --git a/.fossil-settings/empty-dirs b/.fossil-settings/empty-dirs new file mode 100644 index 0000000..f8f80ad --- /dev/null +++ b/.fossil-settings/empty-dirs @@ -0,0 +1,7 @@ +src +src/vendorlib +src/vendormodules +src/modules +src/lib +lib +modules diff --git a/.fossil-settings/ignore-glob b/.fossil-settings/ignore-glob new file mode 100644 index 0000000..170a4f6 --- /dev/null +++ b/.fossil-settings/ignore-glob @@ -0,0 +1,29 @@ +.git +bin +lib +#The directory for compiled/built Tcl modules +modules + +#Temporary files e.g from tests +tmp + +logs +_aside +_build + +#Built documentation +html +man +md +doc + +test* + +#Built tclkits (if any) +punk*.exe +tcl*.exe + +#miscellaneous editor files etc +*.swp + +todo.txt diff --git a/src/make.tcl b/src/make.tcl index 9003050..1f0c034 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -89,6 +89,9 @@ if {[file exists $bootsupport_mod] || [file exists $bootsupport_lib]} { package require punk::mix package forget punk::repo package require punk::repo + package forget punkcheck + package require punkcheck + #restore module paths and auto_path in addition to the bootsupport ones @@ -247,7 +250,7 @@ if {$::punkmake::command eq "get-project-info"} { } if {$::punkmake::command eq "shell"} { - package require pu + #package require pu } @@ -266,14 +269,38 @@ file mkdir $target_modules_base #external libs and modules first - and any supporting files - no 'building' required if {[file exists $sourcefolder/vendorlib]} { - set copied [punk::mix::cli::lib::copy_files_from_source_to_target $sourcefolder/vendorlib $projectroot/lib -overwrite ALL-TARGETS] + #unpublish README.md from source folder - but on the root one + set unpublish [list\ + README.md\ + ] + + set resultdict [punkcheck::install $sourcefolder/vendorlib $projectroot/lib -overwrite installedsourcechanged-targets -unpublish_paths $unpublish] + set copied [dict get $resultdict files_copied] + set sources_unchanged [dict get $resultdict sources_unchanged] + puts stdout "--------------------------" puts stderr "Copied [llength $copied] vendor libs from src/vendorlib to $projectroot/lib" + foreach f $copied { + puts stdout "COPIED $f" + } + puts stdout "[llength $sources_unchanged] unchanged source files" + puts stdout "--------------------------" } else { puts stderr "NOTE: No src/vendorlib folder found." } + + if {[file exists $sourcefolder/vendormodules]} { - set copied [punk::mix::cli::lib::copy_files_from_source_to_target $sourcefolder/vendormodules $target_modules_base -overwrite ALL-TARGETS] + #install .tm *and other files* + set resultdict [punkcheck::install $sourcefolder/vendormodules $target_modules_base -installer make.tcl -overwrite installedsourcechanged-targets -unpublish_paths {README.md}] + set copied [dict get $resultdict files_copied] + set sources_unchanged [dict get $resultdict sources_unchanged] + puts stdout "--------------------------" puts stderr "Copied [llength $copied] vendor modules from src/vendormodules to $target_modules_base" + foreach f $copied { + puts stdout "COPIED $f" + } + puts stdout "[llength $sources_unchanged] unchanged source files" + puts stdout "--------------------------" } else { puts stderr "NOTE: No src/vendormodules folder found." } @@ -282,12 +309,24 @@ if {[file exists $sourcefolder/vendormodules]} { #There may be multiple other src module folders at same level (e.g folder not being other special-purpose folder and not matching name vendor* that contains at least one .tm file in its root) set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $projectroot] foreach src_module_dir $source_module_folderlist { + puts stderr "Processing source module dir: $src_module_dir" set dirtail [file tail $src_module_dir] #modules and associated files belonging to this package/app set copied [punk::mix::cli::lib::build_modules_from_source_to_base $src_module_dir $target_modules_base -glob *.tm] ;#will only accept a glob ending in .tm + #set copied [list] + puts stdout "--------------------------" puts stderr "Copied [llength $copied] tm modules from src/$dirtail to $target_modules_base " - - set copied [punk::mix::cli::lib::copy_nonmodules_from_source_to_base $src_module_dir $target_modules_base -overwrite ALL-TARGETS] + puts stdout "--------------------------" + + set overwrite "installedsourcechanged-targets" + #set overwrite "ALL-TARGETS" + set resultdict [punkcheck::install_non_tm_files $src_module_dir $target_modules_base -installer make.tcl -overwrite $overwrite -unpublish_paths {README.md}] + set copied [dict get $resultdict files_copied] + set sources_unchanged [dict get $resultdict sources_unchanged] + puts stdout "--------------------------" + puts stderr "Copied [llength $copied] non-tm source files from $src_module_dir to $target_modules_base" + puts stderr "[llength $sources_unchanged] unchanged source files" + puts stdout "--------------------------" } # ---------------------------------------- @@ -299,7 +338,7 @@ if {![llength $vfs_folders]} { exit 0 } -set buildfolder [punk::mix::cli::lib::get_build_folder $sourcefolder] +set buildfolder [punk::mix::cli::lib::get_build_workdir $sourcefolder] if {$buildfolder ne "$sourcefolder/_build"} { puts stderr "$sourcefolder/_build doesn't match the project buildfolder $buildfolder - check project filestructure" puts stdout " -aborted- " @@ -327,139 +366,214 @@ if {[llength $runtimes] > 1} { exit 3 } + + +set installername "make.tcl" +# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- set runtimefile [lindex $runtimes 0] #sdx *may* be pointed to use the runtime we use to build the kit, or the user may manually use this runtime if they don't have tclsh #sdx will complain if the same runtime is used for the shell as is used in the -runtime argument - so we make a copy (REVIEW) -if {![file exists $buildfolder/buildruntime.exe]} { - file copy $rtfolder/$runtimefile $buildfolder/buildruntime.exe +#if {![file exists $buildfolder/buildruntime.exe]} { +# file copy $rtfolder/$runtimefile $buildfolder/buildruntime.exe +#} + +set basedir $buildfolder +set config [dict create\ + -make-step copy_runtime\ +] +lassign [punkcheck::start_installer_event $basedir/.punkcheck $installername $rtfolder $buildfolder $config] _eventid punkcheck_eventid _recordset record_list + + +set target_relpath [punkcheck::lib::path_relative $basedir $buildfolder/buildruntime.exe] +set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid] +# -- --- --- --- --- --- +set source_relpath [punkcheck::lib::path_relative $basedir $rtfolder/$runtimefile] +set file_record [punkcheck::installfile_add_source_and_fetch_metadata $basedir $source_relpath $file_record] +# -- --- --- --- --- --- +set changed_unchanged [punkcheck::recordlist::file_install_record_source_changes [lindex [dict get $file_record body] end]] +if {[llength [dict get $changed_unchanged changed]]} { + set file_record [punkcheck::installfile_started_install $basedir $file_record] + # -- --- --- --- --- --- + puts stdout "Copying runtime from $rtfolder/$runtimefile to $buildfolder/buildruntime.exe" + file copy -force $rtfolder/$runtimefile $buildfolder/buildruntime.exe + # -- --- --- --- --- --- + set file_record [punkcheck::installfile_finished_install $basedir $file_record] +} else { + puts stderr "." + set file_record [punkcheck::installfile_skipped_install $basedir $file_record] } +# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + + set startdir [pwd] puts stdout "Found [llength $vfs_folders] .vfs folders - building executable for each..." cd [file dirname $buildfolder] +#root folder mtime is insufficient for change detection. Tree mtime of folders only is a barely passable mechanism for vfs change detection in some circumstances - e.g if files added/removed but never edited in place +#a hash of full tree file & dir mtime may be more reasonable - but it remains to be seen if just tar & checksum is any/much slower. +#Simply rebuilding all the time may be close the speed of detecting change anyway - and almost certainly much faster when there is a change. +#Using first mtime encountered that is later than target is another option - but likely to be highly variable in speed. Last file in the tree could happen to be the latest, and this mechanism doesn't handle build on reversion to older source. foreach vfs $vfs_folders { + set vfsname [file rootname $vfs] puts stdout " Processing vfs $sourcefolder/$vfs" puts stdout " ------------------------------------" + set skipped_vfs_build 0 + # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + set basedir $buildfolder + set config [dict create\ + -make-step build_vfs\ + ] + lassign [punkcheck::start_installer_event $basedir/.punkcheck $installername $sourcefolder $buildfolder $config] _eventid punkcheck_eventid _recordset record_list + + + set target_relpath [punkcheck::lib::path_relative $basedir $buildfolder/$vfsname.exe] + set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid] + # -- --- --- --- --- --- + set source_relpath [punkcheck::lib::path_relative $basedir $sourcefolder/$vfs] + set file_record [punkcheck::installfile_add_source_and_fetch_metadata $basedir $source_relpath $file_record] + # -- --- --- --- --- --- + set changed_unchanged [punkcheck::recordlist::file_install_record_source_changes [lindex [dict get $file_record body] end]] + if {[llength [dict get $changed_unchanged changed]]} { + set file_record [punkcheck::installfile_started_install $basedir $file_record] + # -- --- --- --- --- --- + + if {[file exists $buildfolder/$vfsname]} { + puts stderr "deleting existing $buildfolder/$vfsname" + file delete $sourcefolder/_build/$vfsname + } - if {[file exists $buildfolder/$vfsname]} { - puts stderr "deleting existing $buildfolder/$vfsname" - file delete $sourcefolder/_build/$vfsname - } + puts stdout "building $vfsname with sdx.. vfsdir:$vfs cwd: [pwd]" + + + if {[catch { + exec sdx wrap $buildfolder/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose + } result]} { + puts stderr "sdx wrap _build/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose failed with msg: $result" + } else { + puts stdout "ok - finished sdx" + set separator [string repeat = 40] + puts stdout $separator + puts stdout $result + puts stdout $separator + } + + if {![file exists $buildfolder/$vfsname]} { + puts stderr "|err> build didn't seem to produce output at $sourcefolder/_build/$vfsname" + exit 2 + } - puts stdout "building $vfsname with sdx.. vfsdir:$vfs cwd: [pwd]" - - if {[catch { - exec sdx wrap $buildfolder/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose - } result]} { - puts stderr "sdx wrap _build/$vfsname -vfs $sourcefolder/$vfs -runtime $buildfolder/buildruntime.exe -verbose failed with msg: $result" - } else { - puts stdout "ok - finished sdx" - set separator [string repeat = 40] - puts stdout $separator - puts stdout $result - puts stdout $separator - } - if {![file exists $buildfolder/$vfsname]} { - puts stderr "|err> build didn't seem to produce output at $sourcefolder/_build/$vfsname" - exit 2 - } - if {$::tcl_platform(platform) eq "windows"} { - set pscmd "tasklist" + + # -- --- --- --- --- --- + set file_record [punkcheck::installfile_finished_install $basedir $file_record] } else { - set pscmd "ps" + set skipped_vfs_build 1 + puts stderr "." + puts stdout "Skipping build for vfs $vfs - no change detected" + set file_record [punkcheck::installfile_skipped_install $basedir $file_record] } + # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + - if {![catch { - exec $pscmd | grep $vfsname - } still_running]} { - puts stdout "found $vfsname instances still running\n" - set count_killed 0 - foreach ln [split $still_running \n] { - puts stdout " $ln" - - if {$::tcl_platform(platform) eq "windows"} { - set pid [lindex $ln 1] - if {$forcekill} { - set killcmd [list taskkill /F /PID $pid] + if {!$skipped_vfs_build} { + + if {$::tcl_platform(platform) eq "windows"} { + set pscmd "tasklist" + } else { + set pscmd "ps" + } + + if {![catch { + exec $pscmd | grep $vfsname + } still_running]} { + puts stdout "found $vfsname instances still running\n" + set count_killed 0 + foreach ln [split $still_running \n] { + puts stdout " $ln" + + if {$::tcl_platform(platform) eq "windows"} { + set pid [lindex $ln 1] + if {$forcekill} { + set killcmd [list taskkill /F /PID $pid] + } else { + set killcmd [list taskkill /PID $pid] + } } else { - set killcmd [list taskkill /PID $pid] + set pid [lindex $ln 0] + #review! + if {$forcekill} { + set killcmd [list kill -9 $pid] + } else { + set killcmd [list kill $pid] + } } - } else { - set pid [lindex $ln 0] - #review! - if {$forcekill} { - set killcmd [list kill -9 $pid] + + puts stdout " pid: $pid (attempting to kill now using '$killcmd')" + + if {[catch { + exec {*}$killcmd + } errMsg]} { + puts stderr "$killcmd returned an error:" + puts stderr $errMsg + puts stderr "(try '[info script] -k' option to force kill)" + exit 4 } else { - set killcmd [list kill $pid] + puts stderr "$killcmd ran without error" + incr count_killed } } - - puts stdout " pid: $pid (attempting to kill now using '$killcmd')" - - if {[catch { - exec {*}$killcmd - } errMsg]} { - puts stderr "$killcmd returned an error:" - puts stderr $errMsg - puts stderr "(try '[info script] -k' option to force kill)" - exit 4 - } else { - puts stderr "$killcmd ran without error" - incr count_killed + if {$count_killed > 0} { + puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" + after 1000 } + } else { + puts stderr "Ok.. no running '$vfsname' processes found" } - if {$count_killed > 0} { - puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" - after 1000 - } - } else { - puts stderr "Ok.. no running '$vfsname' processes found" - } - if {$::tcl_platform(platform) eq "windows"} { - set targetexe ${vfsname}.exe - } else { - set targetexe $vfsname - } + if {$::tcl_platform(platform) eq "windows"} { + set targetexe ${vfsname}.exe + } else { + set targetexe $vfsname + } - if {[file exists $buildfolder/$targetexe]} { - puts stderr "deleting existing $buildfolder/$targetexe" - if {[catch { - file delete $sourcefolder/_build/$targetexe - } msg]} { - puts stderr "Failed to delete $buildfolder/$targetexe" - exit 4 + if {[file exists $buildfolder/$targetexe]} { + puts stderr "deleting existing $buildfolder/$targetexe" + if {[catch { + file delete $sourcefolder/_build/$targetexe + } msg]} { + puts stderr "Failed to delete $buildfolder/$targetexe" + exit 4 + } } - } - if {$::tcl_platform(platform) eq "windows"} { - file rename $buildfolder/$vfsname $sourcefolder/_build/${vfsname}.exe - } + if {$::tcl_platform(platform) eq "windows"} { + file rename $buildfolder/$vfsname $sourcefolder/_build/${vfsname}.exe + } - after 200 - set deployment_folder [file dirname $sourcefolder]/bin - file mkdir $deployment_folder + after 200 + set deployment_folder [file dirname $sourcefolder]/bin + file mkdir $deployment_folder - if {[file exists $deployment_folder/$targetexe]} { - puts stderr "deleting existing deployed at $deployment_folder/$targetexe" - if {[catch { - file delete $deployment_folder/$targetexe - } errMsg]} { - puts stderr "deletion of deployed version at $deployment_folder/$targetexe failed: $errMsg" - exit 5 + if {[file exists $deployment_folder/$targetexe]} { + puts stderr "deleting existing deployed at $deployment_folder/$targetexe" + if {[catch { + file delete $deployment_folder/$targetexe + } errMsg]} { + puts stderr "deletion of deployed version at $deployment_folder/$targetexe failed: $errMsg" + exit 5 + } } - } - puts stdout "copying.." - puts stdout "$buildfolder/$targetexe" - puts stdout "to:" - puts stdout "$deployment_folder/$targetexe" - after 500 - file copy $buildfolder/$targetexe $deployment_folder/$targetexe + puts stdout "copying.." + puts stdout "$buildfolder/$targetexe" + puts stdout "to:" + puts stdout "$deployment_folder/$targetexe" + after 500 + file copy $buildfolder/$targetexe $deployment_folder/$targetexe + } } cd $startdir diff --git a/src/mixtemplates/module/template_unversioned.tm b/src/mixtemplates/module/template_unversioned.tm new file mode 100644 index 0000000..5b20b95 --- /dev/null +++ b/src/mixtemplates/module/template_unversioned.tm @@ -0,0 +1,50 @@ +# -*- tcl -*- +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) %year% +# +# @@ Meta Begin +# Application %pkg% %version% +# Meta platform tcl +# Meta license %license% +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +##e.g package require frobz + + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval %pkg% { + + + + +} + + + + + + + + + + + + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide %pkg% [namespace eval %pkg% { + variable version + set version %version% +}] +return \ No newline at end of file diff --git a/src/modules/canaryspace-999999.0a1.0.tm b/src/modules/canaryspace-999999.0a1.0.tm new file mode 100644 index 0000000..42184d4 --- /dev/null +++ b/src/modules/canaryspace-999999.0a1.0.tm @@ -0,0 +1,70 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from -buildversion.txt +# +# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. +# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# (C) 2023 +# +# @@ Meta Begin +# Application canaryspace 999999.0a1.0 +# Meta platform tcl +# Meta license BSD +# Meta summary Diagnostic tool for namespace navigation/introspection to help avoid command conflicts. +# Meta description canaryspace loads the ::canaryspace namespace with wrappers for the set of commands +# Meta description that exist in the global namespace :: at the time the canaryspace package is loaded. +# Meta description These commands just emit info to stderr to assist in determining whether calls are +# Meta description unintentionally being run in the namespace. +# Meta description This is often the case with commands which use uplevel 1 or similar constructs to call +# Meta description code in the callers namespace. If such commands need to run in arbitrary namespaces +# Meta description which may have arbitrary commands then uplevelled commands may need to be prefixed with +# Meta description :: or the appropriate namespace path. +# Meta description Constructs such as punk pipelines deliberately run script segments in the calling context +# Meta description and so may need to be comprised mainly of fully qualified commands. +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +##e.g package require frobz + + +namespace eval canaryspace::setup { + variable gcommands + proc build_commands {} { + variable gcommands + gcommands.= nscommands ::* -raw |> .=>1 linelist + foreach cmd $gcommands { + proc ::canaryspace::$cmd args [string map [list $cmd] { + ::puts stderr "CANARYSPACE " + ::puts stderr " [::info level 0]" + ::tailcall :: {*}$args + } ] + } + } + build_commands +} + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +namespace eval canaryspace { + + +} + + + + + + + + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide canaryspace [namespace eval canaryspace { + ::variable version + ::set version 999999.0a1.0 +}] +return \ No newline at end of file diff --git a/src/modules/canaryspace-buildversion.txt b/src/modules/canaryspace-buildversion.txt new file mode 100644 index 0000000..f47d01c --- /dev/null +++ b/src/modules/canaryspace-buildversion.txt @@ -0,0 +1,3 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. diff --git a/src/modules/patternpunk-1.1.tm b/src/modules/patternpunk-1.1.tm index 573c8a2..0f15830 100644 --- a/src/modules/patternpunk-1.1.tm +++ b/src/modules/patternpunk-1.1.tm @@ -174,6 +174,17 @@ set ::punk::bannerTemplate { _+_+ @ } +>punk .. Property fossil { + .. + > < + \ / v + v \\_/ + \/\\ v . + v_ /|\/ / + \__/ +} + + >punk .. Method dumpProperties {{object ::>punk}} { foreach {p v} [$object .. Properties . pairs] { puts $p diff --git a/src/modules/punk-0.1.tm b/src/modules/punk-0.1.tm index 6c200fc..0d7cba2 100644 --- a/src/modules/punk-0.1.tm +++ b/src/modules/punk-0.1.tm @@ -2,6 +2,15 @@ #Built on Tcl of course - because it's the most powerful piece of under-appreciated and alternate-thinking engineering you can plug into. +namespace eval punk { + variable twapi_loader_tid + package require Thread + set twapi_loader_tid [thread::create] + thread::send -async $twapi_loader_tid {package require twapi} + + +} + #repltelemetry cooperation with other packages such as shellrun @@ -67,6 +76,7 @@ package require punk::config package require punk::winpath ;# for windows paths - but has functions that can be called on unix systems package require punk::repo package require punk::du +package require punk::mix::base namespace eval punk { interp alias {} purelist {} lreplace x 0 0 ;#required by pipe system @@ -110,14 +120,55 @@ namespace eval punk { debug header "dbg> " + + variable last_run_display [list] variable colour_disabled 0 variable ns_current "::" #variable re_headvar1 {([a-zA-Z:@.(),]+?)(?![^(]*\))(,.*)*$} - + + + #----------------------------------------------------------------------------------- + #strlen is important for testing issues with string representationa and shimmering. + #This specific implementation with append (as at 2023-09) is designed to ensure the original str representation isn't changed + #It may need to be reviewed with different Tcl versions in case the append empty string is 'optimised/tuned' in some way that affects the behaviour + proc strlen {str} { + append str2 $str {} + string length $str2 + } + + #get a copy of the item without affecting internal rep + proc objclone {obj} { + append obj2 $obj {} + } + interp alias "" strlen "" ::punk::strlen + interp alias "" objclone "" ::punk::objclone + #proc ::strlen {str} { + # string length [append str2 $str {}] + #} + #proc ::objclone {obj} { + # append obj2 $obj {} + #} + #----------------------------------------------------------------------------------- proc ::punk::K {x y} { return $x} + proc ::punk::uuid {} { + set has_twapi 0 + if {"windows" eq $::tcl_platform(platform)} { + if {![catch {package require twapi}]} { + set has_twapi 1 + } + } + 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" + } + return [uuid::uuid generate] + } else { + return [twapi::new_uuid] + } + } proc ::punk::var {varname {= {}} args} { upvar $varname the_var if {${=} == "="} { @@ -2170,7 +2221,7 @@ namespace eval punk { lset var_actions $i 1 matchvar-set #attempt to read upvar $lvlup $varname the_var - #if {![catch {uplevel $lvlup [list set $varname]} existingval]} {} + #if {![catch {uplevel $lvlup [list ::set $varname]} existingval]} {} if {![catch {set the_var} existingval]} { if {$isbool} { @@ -2760,8 +2811,12 @@ namespace eval punk { upvar $pipevarname the_pipe set the_pipe $args } + + #pipealias should capture the namespace context of the pipeline so that commands are resolved in the namespace in which the pipealias is created proc pipealias {targetcmd args} { - tailcall interp alias {} $targetcmd {} {*}$args + set cmdcopy [punk::objclone $args] + set nscaller [uplevel 1 [list namespace current]] + tailcall interp alias {} $targetcmd {} apply [list args [append cmdcopy " {*}\$args"] $nscaller] } #same as used in unknown func for initial launch @@ -2783,7 +2838,7 @@ namespace eval punk { #pipecmd could have glob chars - test $pipcmd in the list - not just that info commands returns results. if {$pipecmd in [info commands $pipecmd]} { #puts "==nscaller: '[uplevel 1 [list namespace current]]'" - uplevel 1 [list namespace import $pipecmd] + uplevel 1 [list ::namespace import $pipecmd] tailcall $pipecmd {*}$args } @@ -2943,8 +2998,8 @@ namespace eval punk { } debug.punk.pipe.compile {match_assign creating proc $pipecmd} 2 - uplevel 1 [list proc $pipecmd args $script] - uplevel 1 [list namespace import $pipecmd] + uplevel 1 [list ::proc $pipecmd args $script] + uplevel 1 [list ::namespace import $pipecmd] tailcall $pipecmd {*}$args } @@ -3736,6 +3791,7 @@ namespace eval punk { #puts stderr " script: $script" #puts stderr " vals: $segmentargvals $argsdatalist" #pipeline script context should be one below calling context - so upvar v v will work + #ns with leading colon will fail with apply set evaluation [uplevel 1 [list ::apply [::list [::concat $segmentargnames args] $script $ns] {*}$segmentargvals {*}$argsdatalist]] } @@ -4107,7 +4163,7 @@ namespace eval punk { tailcall $pattern=$equalsrhs {*}$tail } } - #puts "--->nscurrent [uplevel 1 [list namespace current]]" + #puts "--->nscurrent [uplevel 1 [list ::namespace current]]" #ignore the namespace.. #We could interpret the fact that the nonexistant pipe was called with a namespace to indicate that's where the pipecommand should be created.. #But.. we would need to ensure 1st (compiling) invocation runs the same way as subsequent invocations. @@ -4407,7 +4463,7 @@ namespace eval punk { set cmdlist [list ::punk::pipeline .= "" "" {*}$arglist] } elseif {$assign eq "="} { #set cmdlist [list ::punk::pipeline = "" "" {*}$arglist] - set cmdlist [list = {*}$arglist] + set cmdlist [list ::= {*}$arglist] } elseif {![punk::arg_is_script_shaped $assign] && [string first "=" $assign] >= 0} { #set re_dotequals {^([^ \t\r\n=\{]*)\.=$} #set re_equals {^([^ \t\r\n=\{]*)=$} @@ -4426,7 +4482,7 @@ namespace eval punk { } - if {[catch {uplevel 1 [list if 1 $cmdlist]} result erroptions]} { + if {[catch {uplevel 1 [list ::if 1 $cmdlist]} result erroptions]} { #puts stderr "====>>> result: $result erroptions" set ecode [dict get $erroptions -errorcode] if {[lindex $ecode 0] eq "pipesyntax"} { @@ -4633,7 +4689,7 @@ namespace eval punk { #useful for aliases e.g treemore -> xmore tree proc xmore {args} { if {[llength $args]} { - {*}$args | more + uplevel #0 [list {*}$args | more] } else { error "usage: punk::xmore args where args are run as {*}\$args | more" } @@ -4946,6 +5002,7 @@ namespace eval punk { tailcall run echo {*}$args } proc sh_ECHO {args} { + #execute the result of the run command - which is something like: 'exitcode n' - to get true/false tailcall apply {arglist {uplevel #0 [run echo {*}$arglist]} ::} $args } @@ -4967,11 +5024,13 @@ namespace eval punk { } #------------------------------------------------------------------- - namespace export help aliases alias nsjoin nsprefix cdwin cdwindir dirfiles dirfiles_dict exitcode windir % pipedata pipecase pipeline pipematch pipeswitch pipeswitchc pipecase linelist linesort inspect list_as_lines is_list_all_in_list is_list_all_ni_list val treemore - namespace ensemble create + namespace export help aliases alias nsjoin nsprefix dirfiles dirfiles_dict exitcode % pipedata pipecase pipeline pipematch pipeswitch pipeswitchc pipecase linelist linesort inspect list_as_lines is_list_all_in_list is_list_all_ni_list val treemore + + #namespace ensemble create proc hasglobs {str} { - expr {[string first * $str]>=0 || [string first ? $str]>=0} + regexp {[*?]} [append obj2 $str {}] ;# int-rep preserving + #expr {[string first * $str]>=0 || [string first ? $str]>=0} } @@ -4989,10 +5048,9 @@ namespace eval punk { set targetns [nsprefix $path] set name [nstail $path] } else { - set thispath [uplevel 1 [list nsthis $path]] + set thispath [uplevel 1 [list ::nsthis $path]] set targetns [nsprefix $thispath] set name [nstail $thispath] - #set upns [uplevel 1 [list namespace current]] } #puts stderr "corp upns:$upns" @@ -5029,7 +5087,7 @@ namespace eval punk { } } if {[nsprefix $origin] ne [nsprefix [nsjoin ${targetns} $name]]} { - append body "# namespace origin $origin" + append body "# namespace origin $origin" \n } append body [info body $origin] @@ -5045,14 +5103,18 @@ namespace eval punk { proc nsjoin {prefix name} { if {[string match ::* $name]} { - if {[string length $prefix]} { + if {"$prefix" ne ""} { error "nsjoin: won't join non-empty prefix to absolute namespace path '$name'" } return $name } - if {$prefix eq "::"} { + if {"$prefix" eq "::"} { return ::$name } + #if {"$name" eq ""} { + # return $prefix + #} + #nsjoin ::x::y "" should return ::x::y:: - this is the correct fully qualified form used to call a command that is the empty string return ${prefix}::$name } proc nsjoinall {prefix args} { @@ -5079,18 +5141,27 @@ namespace eval punk { } return [join $nonempty_segments ::] } - proc nsprefix {{name ""}} { - set rawprefix [string range $name 0 end-[string length [punk::nstail $name]]] + proc nsprefix {{nspath ""}} { + #normalize the common case of :::: + set nspath [string map [list :::: ::] $nspath] + set rawprefix [string range $nspath 0 end-[string length [punk::nstail $nspath]]] if {$rawprefix eq "::"} { return $rawprefix } else { - return [string trimright $rawprefix :] + if {[string match *:: $rawprefix]} { + return [string range $rawprefix 0 end-2] + } else { + return $rawprefix + } + #return [string trimright $rawprefix :] } } #namespace tail which handles :::cmd ::x:::y ::x:::/y etc #todo - raise error for unexpected sequences such as :::: or more than 2 colons together. proc nstail {nspath args} { + #normalize the common case of :::: + set nspath [string map [list :::: ::] $nspath] set mapped [string map [list :: \u0FFF] $nspath] set parts [split $mapped \u0FFF] @@ -5111,19 +5182,25 @@ namespace eval punk { } #return a list of namespace segments - always with leading empty string for fully qualified namespace (ie for ::x) - #'supports' weird namespaces /commands such as :x :::x ::x:::y - #Can be used to either suppor use of such namespaces/commands - or as part of validation to disallow them + #'supports' to some extent unreasonable namespaces /commands such as x: ::x: ::x:::y + #Can be used to either support use of such namespaces/commands - or as part of validation to disallow them #as opposed to silent behaviour of Tcl namespace commands which don't handle them consistently (for tcl 8.x anyway Review tcl 9) #Note that for ::x:: the trailing :: cannot represent a trailing namespace part being an empty string #This is because Tcl's 'namespace eval "" ""' reports 'only global namespace can have empty name' - # + #NOTE tcl allows creating ambiguous namespaces. e.g ::punk:::etc:::blah + # is this :: punk :etc :blah or :: punk :etc: blah + #clearly leading/trailing colons in namespaces and commands are just a bad idea. + #nsparts will prefer leading colon (ie greedy on ::) + #This is important to support leading colon commands such as :/ + # ie ::punk:::jjj:::etc -> :: punk :jjj :etc proc nsparts {nspath} { - set mapped [string map [list :: \u0FFF] $nspath] - set parts [split $mapped \u0FFF] - if {[lindex $parts end] eq ""} { + set nspath [string map [list :::: ::] $nspath] + set mapped [string map [list :: \u0FFF] $nspath] + set parts [split $mapped \u0FFF] + if {[lindex $parts end] eq ""} { - } - return $parts + } + return $parts } #review ??? @@ -5138,13 +5215,29 @@ namespace eval punk { } #tilde - interp alias {} ~ {} file join $::env(HOME) ;#HOME must be capitalized to work cross platform (lowercase home works on windows - but probably not elsewhere) + #These aliases work fine for interactive use - but the result is always a string int-rep + #interp alias {} ~ {} file join $::env(HOME) ;#HOME must be capitalized to work cross platform (lowercase home works on windows - but probably not elsewhere) #interp alias {} ~ {} apply {args {file join $::env(HOME) $args}} + proc ~ {args} { + set hdir [punk::objclone $::env(HOME)] + file pathtype $hdir + set d $hdir + #use the file join 2-arg optimisation to avoid losing path-rep - probably doesn't give any advantage on all Tcl versions + foreach a $args { + set d [file join $d $a] + } + file pathtype $d + return [punk::objclone $d] + } + interp alias {} ~ {} punk::~ + interp alias {} nsjoin {} punk::nsjoin interp alias {} nsprefix {} punk::nsprefix interp alias {} nstail {} punk::nstail + interp alias {} nsparts {} punk::nsparts + interp alias {} nstree {} punk::nstree #tcl 8.x has creative writing var weirdness.. tcl 9 is likely to differ proc nsvars {{nsglob "*"}} { @@ -5204,7 +5297,7 @@ namespace eval punk { set nspath [nsjoinall $ns_current {*}$args] } - set ns_exists [punk::nseval [punk::nsprefix $nspath] [list namespace exists [punk::nstail $nspath] ]] + set ns_exists [punk::nseval [punk::nsprefix $nspath] [list ::namespace exists [punk::nstail $nspath] ]] if {$ns_exists} { error "Namespace $nspath already exists" @@ -5241,16 +5334,18 @@ namespace eval punk { #is there ever any difference to {namespace current}? #interp alias {} nsthis {} .= .= namespace code {namespace current} |> .=* <0/#| #interp alias {} nsthis {} namespace current + interp alias {} nsthis {} punk::nspath_here_absolute - proc nspath_here_absolute {{nspath ""}} { + proc nspath_here_absolute {{nspath "\uFFFF"}} { set path_is_absolute [expr {[string match ::* $nspath]}] if {$path_is_absolute} { return $nspath } - set ns_caller [uplevel 1 {namespace current}] - if {![string length $nspath]} { + set ns_caller [uplevel 1 {::namespace current}] ;#must qualify. Needs to be called from arbitrary namespaces where 'namespace' command may exist (e.g tdom::schema) + if {$nspath eq "\uFFFF"} { return $ns_caller } + #nsjoin will join nscaller with empty nspath to form nscaller:: - which is correct way to represent command named with empty string return [punk::nsjoin $ns_caller $nspath] } @@ -5268,7 +5363,7 @@ namespace eval punk { #cli command - impure - relies on caller/ns_current proc nslist_dict {{glob "*"}} { set ns_absolute [uplevel 1 [list punk::nspath_here_absolute $glob]] - return [get_nslist_dict $ns_absolute] + return [get_ns_dicts $ns_absolute] } proc nslist_dict1 {{glob "*"}} { variable ns_current ;#keep fully qualified ie :: or ::etc @@ -5326,7 +5421,7 @@ namespace eval punk { set i 0 set tails [lrepeat [llength $parts] ""] foreach ns $parts { - set cmdlist [list namespace eval $ns] + set cmdlist [list ::namespace eval $ns] set t "" if {$i > 0} { append body " " @@ -5354,36 +5449,244 @@ namespace eval punk { set body [string map [list