put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
}
}
return
return
}
}
#A batch file with unix line-endings is sensitive to label positioning.
#A batch file with unix line-endings is sensitive to label positioning.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#scriptset name to substiture multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
proc multishell {filepath_or_scriptset args} {
#set usage ""
set opts [dict create\
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
-askme 1\
#append usage "The scriptset name will be used to search for <scriptsetname>.sh|.tcl|.ps1 or names as you specify in yourname.wrapconfig if it exists" \n
-outputfolder "\uFFFF"\
#append usage "If no template is specified in a .wrapconfig and no -template argument is supplied, it will default to punk-multishell.cmd" \n
-template "\uFFFF"\
#if {![string length $filepath_or_scriptset]} {
-returnextra 0\
# puts stderr "No filepath_or_scriptset specified"
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_askme [dict get $opts -askme]
set filepath_or_scriptset [dict get $leaders filepath_or_scriptset]
set opt_template [dict get $opts -template]
set opt_askme [dict get $opts -askme]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_template [dict get $opts -template] ;#use dict exists $received -template to see if overridable in .toml
set opt_returnextra [dict get $opts -returnextra]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_force [dict get $opts -force]
set opt_returnextra [dict get $opts -returnextra]
set opt_force [dict get $opts -force]
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set ext [file extension $filepath_or_scriptset]
set ext [file extension $filepath_or_scriptset]
set startdir [pwd]
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
if {[file pathtype $filepath_or_scriptset] ni {absolute relative}} {
error "bad pathtype for '$filepath_or_scriptset' (expected absolute or relative path, or name of scriptset)"
}
#first check if absolute path matches a file or relative path from cwd matches a file
#first check if relative or absolute path matches a file
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
set specified_path $filepath_or_scriptset
set specified_path $filepath_or_scriptset
} else {
} else {
set specified_path [file join $startdir $filepath_or_scriptset]
set specified_path [file join $startdir $filepath_or_scriptset]
}
}
set scriptdir [file dirname $specified_path]
set ext [string trim [file extension $filepath_or_scriptset] .]
set ext [string trim [file extension $filepath_or_scriptset] .]
set allowed_extensions [list wrapconfig tcl ps1 sh bash pl]
set scriptset ""
set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
if {$ext eq ""} {
#set allowed_extensions [list tcl]
set scriptset [file rootname [file tail $specified_path]]
set found_script 0
} elseif {$ext eq "toml"} {
if {[file exists $specified_path]} {
set tomltail [file tail $specified_path]
set found_script 1
if {[string match *_wrap.toml $tomltail]} {
set scriptset [lindex [split $tomltail _] 0]
#if .toml was specified - the config file must exist
if {![file exists $specified_path]} {
if {[file pathtype $filepath_or_scriptset] eq "relative"} {
puts stderr "unable to locate '$specified_path' - will continue search in src/scriptapps folder"
} else {
#caller was specific about path - no fallback to src/scriptapps
error "unable to locate '$specified_path'"
}
}
} else {
error "supplied toml file must be of form <scriptset>_wrap.toml"
}
} else {
} else {
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {$ext ni $allowed_extensions} {
if {[file exists $filepath_or_scriptset.$e]} {
error "supplied filepath_or_scriptset must be the name of a scriptset without extension, a file named <scriptset>_wrap.toml, or a script with one of the extensions: $allowed_extensions"
set found_script 1
}
break
}
set list_input_files [list]
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
puts stderr "No input script files defined in {$scriptset}_wrap.toml"
return false
}
} else {
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
puts stderr "$scriptset.$e"
}
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {[file exists $scriptdir/$scriptset.$e]} {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
}
}
}
} else {
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
}
}
set found_script [expr {[llength $list_input_files] > 0}]
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
set scriptset [file rootname [file tail $specified_path]]
if {$found_script} {
if {$found_script} {
if {[file type $specified_path] eq "file"} {
#found scripts at absolute path - or path relative to cwd
set specified_root [file dirname $specified_path]
set scriptroot $scriptdir
set pathinfo [punk::repo::find_repos [file dirname $specified_path]]
set pathinfo [punk::repo::find_repos $scriptroot]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
if {[string length $projectroot]} {
if {[string length $projectroot]} {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
set customwrapper_folder $projectroot/src/scriptapps/wrappers
set scriptroot [file dirname $specified_path]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
}
} else {
} else {
#outside of any project
#outside of any project
set scriptroot [file dirname $specified_path]
set customwrapper_folder ""
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#no customwrapper folder available
set customwrapper_folder ""
}
}
}
} else {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path."
puts stderr $usage
return false
}
}
} else {
} else {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
return false
}
set pathinfo [punk::repo::find_repos $startdir]
set pathinfo [punk::repo::find_repos $startdir]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[string length $projectroot]} {
if {![string length $projectroot]} {
if {[llength [file split $filepath_or_scriptset]] > 1} {
puts stderr "No matching scripts or config found for $filepath_or_scriptset, and you are not within a directory where projectroot and src/scriptapps can be determined"
puts stderr "filepath_or_scriptset looks like a path - but doesn't seem to point to a file"
return false
puts stderr "Ensure you are within a project and use just the name of the scriptset, or pass in the full correct path or relative path to current directory"
}
puts stderr $usage
return false
set scriptroot $projectroot/src/scriptapps
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#we've already ruled out empty string - so must have a single element representing scriptset - possibly with file extension
#check something matches the scriptset..
set scriptroot $projectroot/src/scriptapps
if {$scriptset ne ""} {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#.toml file may or may not exist
#check something matches the scriptset..
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
set something_found ""
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
if {[file exists $scriptroot/$scriptset]} {
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set found_script 1
if {[dict exists $configd scripts]} {
set something_found $scriptroot/$scriptset ;#extensionless file - that's ok too
set configured_scripts [dict get $configd scripts]
puts stderr "filepath_or_scriptset parameter doesn't seem to refer to a file, and you are not within a directory where projectroot and src/scriptapps/wrappers can be determined"
#expect a single script
puts stderr $usage
if {[file exists $scriptroot/$filepath_or_scriptset]} {
return false
if {[file type $scriptroot/$filepath_or_scriptset] ne "file"} {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path. path: $scriptroot/$filepath_or_scriptset"
if {$process_extensions eq "ALLFOUNDORCONFIGURED"} {
#todo
#todo - look for .wrapconfig or all extensions for the scriptset
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
puts stderr "Sorry - only single input file supported. Supply a file extension or use a .wrapconfig with a single input file for now - implementation incomplete"
#- comparing namespaces_before vs namespaces_after only works if the package was not previously loaded
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
#we could either go to the somewhat expensive route of steaming up an interp with the same auto_path & tcl::tm::list each time..
if {![tcl::namespace::exists $pkg_or_existing_ns]} {
#or cache the result of the namespace we picked for later pkguse calls (pkguse_package_to_namespace dict)
set ver [package require $pkg_unqualified]
#we are using the cache method - but this also doesn't help for packages previously loaded by normal package require
} else {
#our aim is for pkguse <pkgname> to be deterministic in what namespace it finds - even if it doesn't always get the ideal one (e.g cookiejar, see below)
set ver ""
#To determine appropriate namespace for already loaded packages where we have no cache entry - we may still need the helper interp mechanism
}
#The helper interp could be persistent - but only so long as the auto_path/tcl::tm::list values are in sync
#review.
#also see img::png img::raw etc
#these don't directly load namespaces or direct commands.. just change behaviour of existing commands?
#but they can load things like tk (ttk namespace) first one creates ::tkimg?
if {[string match ::* $pkg_or_existing_ns] && [tcl::namespace::exists $pkg_or_existing_ns]} {
#pkguse on an existing full qualified namespace does no package require
set ns $pkg_or_existing_ns
set ns $pkg_or_existing_ns
set ver ""
} else {
} else {
set pkg_unqualified $pkg_or_existing_ns
if {[string match ::* $pkg_or_existing_ns]} {
set ver [package require $pkg_unqualified]
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
set ns ::$pkg_unqualified
} else {
}
set pkg_unqualified $pkg_or_existing_ns
#some packages don't create their namespace immediately and/or don't populate it with commands and instead put entries in ::auto_index
}
set previous_command_count 0
if {[namespace exists $ns]} {
set previous_command_count [llength [info commands ${ns}::*]]
}
#foreach equiv of while 1 - just to allow early exit with break
foreach code_block single {
if {[dict exists $pkguse_package_to_namespace $pkg_unqualified]} {
set ns [dict get $pkguse_package_to_namespace $pkg_unqualified]
set ver [package provide $pkg_unqualified]
break
}
if {[package provide $pkg_unqualified] ne ""} {
#package has already been loaded
if {[namespace exists ::$pkg_unqualified]} {
set ns ::$pkg_unqualified
set ver [package provide $pkg_unqualified]
dict set pkguse_package_to_namespace $pkg_unqualified $ns
break
}
#existing package but no matching namespace..
#- load in throwaway interp and see what cmds/namespaces created
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
}
}
return
return
}
}
#A batch file with unix line-endings is sensitive to label positioning.
#A batch file with unix line-endings is sensitive to label positioning.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#scriptset name to substiture multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
proc multishell {filepath_or_scriptset args} {
#set usage ""
set opts [dict create\
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
-askme 1\
#append usage "The scriptset name will be used to search for <scriptsetname>.sh|.tcl|.ps1 or names as you specify in yourname.wrapconfig if it exists" \n
-outputfolder "\uFFFF"\
#append usage "If no template is specified in a .wrapconfig and no -template argument is supplied, it will default to punk-multishell.cmd" \n
-template "\uFFFF"\
#if {![string length $filepath_or_scriptset]} {
-returnextra 0\
# puts stderr "No filepath_or_scriptset specified"
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_askme [dict get $opts -askme]
set filepath_or_scriptset [dict get $leaders filepath_or_scriptset]
set opt_template [dict get $opts -template]
set opt_askme [dict get $opts -askme]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_template [dict get $opts -template] ;#use dict exists $received -template to see if overridable in .toml
set opt_returnextra [dict get $opts -returnextra]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_force [dict get $opts -force]
set opt_returnextra [dict get $opts -returnextra]
set opt_force [dict get $opts -force]
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set ext [file extension $filepath_or_scriptset]
set ext [file extension $filepath_or_scriptset]
set startdir [pwd]
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
if {[file pathtype $filepath_or_scriptset] ni {absolute relative}} {
error "bad pathtype for '$filepath_or_scriptset' (expected absolute or relative path, or name of scriptset)"
}
#first check if absolute path matches a file or relative path from cwd matches a file
#first check if relative or absolute path matches a file
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
set specified_path $filepath_or_scriptset
set specified_path $filepath_or_scriptset
} else {
} else {
set specified_path [file join $startdir $filepath_or_scriptset]
set specified_path [file join $startdir $filepath_or_scriptset]
}
}
set scriptdir [file dirname $specified_path]
set ext [string trim [file extension $filepath_or_scriptset] .]
set ext [string trim [file extension $filepath_or_scriptset] .]
set allowed_extensions [list wrapconfig tcl ps1 sh bash pl]
set scriptset ""
set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
if {$ext eq ""} {
#set allowed_extensions [list tcl]
set scriptset [file rootname [file tail $specified_path]]
set found_script 0
} elseif {$ext eq "toml"} {
if {[file exists $specified_path]} {
set tomltail [file tail $specified_path]
set found_script 1
if {[string match *_wrap.toml $tomltail]} {
set scriptset [lindex [split $tomltail _] 0]
#if .toml was specified - the config file must exist
if {![file exists $specified_path]} {
if {[file pathtype $filepath_or_scriptset] eq "relative"} {
puts stderr "unable to locate '$specified_path' - will continue search in src/scriptapps folder"
} else {
#caller was specific about path - no fallback to src/scriptapps
error "unable to locate '$specified_path'"
}
}
} else {
error "supplied toml file must be of form <scriptset>_wrap.toml"
}
} else {
} else {
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {$ext ni $allowed_extensions} {
if {[file exists $filepath_or_scriptset.$e]} {
error "supplied filepath_or_scriptset must be the name of a scriptset without extension, a file named <scriptset>_wrap.toml, or a script with one of the extensions: $allowed_extensions"
set found_script 1
}
break
}
set list_input_files [list]
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
puts stderr "No input script files defined in {$scriptset}_wrap.toml"
return false
}
} else {
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
puts stderr "$scriptset.$e"
}
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {[file exists $scriptdir/$scriptset.$e]} {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
}
}
}
} else {
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
}
}
set found_script [expr {[llength $list_input_files] > 0}]
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
set scriptset [file rootname [file tail $specified_path]]
if {$found_script} {
if {$found_script} {
if {[file type $specified_path] eq "file"} {
#found scripts at absolute path - or path relative to cwd
set specified_root [file dirname $specified_path]
set scriptroot $scriptdir
set pathinfo [punk::repo::find_repos [file dirname $specified_path]]
set pathinfo [punk::repo::find_repos $scriptroot]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
if {[string length $projectroot]} {
if {[string length $projectroot]} {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
set customwrapper_folder $projectroot/src/scriptapps/wrappers
set scriptroot [file dirname $specified_path]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
}
} else {
} else {
#outside of any project
#outside of any project
set scriptroot [file dirname $specified_path]
set customwrapper_folder ""
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#no customwrapper folder available
set customwrapper_folder ""
}
}
}
} else {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path."
puts stderr $usage
return false
}
}
} else {
} else {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
return false
}
set pathinfo [punk::repo::find_repos $startdir]
set pathinfo [punk::repo::find_repos $startdir]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[string length $projectroot]} {
if {![string length $projectroot]} {
if {[llength [file split $filepath_or_scriptset]] > 1} {
puts stderr "No matching scripts or config found for $filepath_or_scriptset, and you are not within a directory where projectroot and src/scriptapps can be determined"
puts stderr "filepath_or_scriptset looks like a path - but doesn't seem to point to a file"
return false
puts stderr "Ensure you are within a project and use just the name of the scriptset, or pass in the full correct path or relative path to current directory"
}
puts stderr $usage
return false
set scriptroot $projectroot/src/scriptapps
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#we've already ruled out empty string - so must have a single element representing scriptset - possibly with file extension
#check something matches the scriptset..
set scriptroot $projectroot/src/scriptapps
if {$scriptset ne ""} {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#.toml file may or may not exist
#check something matches the scriptset..
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
set something_found ""
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
if {[file exists $scriptroot/$scriptset]} {
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set found_script 1
if {[dict exists $configd scripts]} {
set something_found $scriptroot/$scriptset ;#extensionless file - that's ok too
set configured_scripts [dict get $configd scripts]
puts stderr "filepath_or_scriptset parameter doesn't seem to refer to a file, and you are not within a directory where projectroot and src/scriptapps/wrappers can be determined"
#expect a single script
puts stderr $usage
if {[file exists $scriptroot/$filepath_or_scriptset]} {
return false
if {[file type $scriptroot/$filepath_or_scriptset] ne "file"} {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path. path: $scriptroot/$filepath_or_scriptset"
if {$process_extensions eq "ALLFOUNDORCONFIGURED"} {
#todo
#todo - look for .wrapconfig or all extensions for the scriptset
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
puts stderr "Sorry - only single input file supported. Supply a file extension or use a .wrapconfig with a single input file for now - implementation incomplete"
#- comparing namespaces_before vs namespaces_after only works if the package was not previously loaded
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
#we could either go to the somewhat expensive route of steaming up an interp with the same auto_path & tcl::tm::list each time..
if {![tcl::namespace::exists $pkg_or_existing_ns]} {
#or cache the result of the namespace we picked for later pkguse calls (pkguse_package_to_namespace dict)
set ver [package require $pkg_unqualified]
#we are using the cache method - but this also doesn't help for packages previously loaded by normal package require
} else {
#our aim is for pkguse <pkgname> to be deterministic in what namespace it finds - even if it doesn't always get the ideal one (e.g cookiejar, see below)
set ver ""
#To determine appropriate namespace for already loaded packages where we have no cache entry - we may still need the helper interp mechanism
}
#The helper interp could be persistent - but only so long as the auto_path/tcl::tm::list values are in sync
#review.
#also see img::png img::raw etc
#these don't directly load namespaces or direct commands.. just change behaviour of existing commands?
#but they can load things like tk (ttk namespace) first one creates ::tkimg?
if {[string match ::* $pkg_or_existing_ns] && [tcl::namespace::exists $pkg_or_existing_ns]} {
#pkguse on an existing full qualified namespace does no package require
set ns $pkg_or_existing_ns
set ns $pkg_or_existing_ns
set ver ""
} else {
} else {
set pkg_unqualified $pkg_or_existing_ns
if {[string match ::* $pkg_or_existing_ns]} {
set ver [package require $pkg_unqualified]
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
set ns ::$pkg_unqualified
} else {
}
set pkg_unqualified $pkg_or_existing_ns
#some packages don't create their namespace immediately and/or don't populate it with commands and instead put entries in ::auto_index
}
set previous_command_count 0
if {[namespace exists $ns]} {
set previous_command_count [llength [info commands ${ns}::*]]
}
#foreach equiv of while 1 - just to allow early exit with break
foreach code_block single {
if {[dict exists $pkguse_package_to_namespace $pkg_unqualified]} {
set ns [dict get $pkguse_package_to_namespace $pkg_unqualified]
set ver [package provide $pkg_unqualified]
break
}
if {[package provide $pkg_unqualified] ne ""} {
#package has already been loaded
if {[namespace exists ::$pkg_unqualified]} {
set ns ::$pkg_unqualified
set ver [package provide $pkg_unqualified]
dict set pkguse_package_to_namespace $pkg_unqualified $ns
break
}
#existing package but no matching namespace..
#- load in throwaway interp and see what cmds/namespaces created
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
}
}
return
return
}
}
#A batch file with unix line-endings is sensitive to label positioning.
#A batch file with unix line-endings is sensitive to label positioning.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#scriptset name to substiture multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
proc multishell {filepath_or_scriptset args} {
#set usage ""
set opts [dict create\
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
-askme 1\
#append usage "The scriptset name will be used to search for <scriptsetname>.sh|.tcl|.ps1 or names as you specify in yourname.wrapconfig if it exists" \n
-outputfolder "\uFFFF"\
#append usage "If no template is specified in a .wrapconfig and no -template argument is supplied, it will default to punk-multishell.cmd" \n
-template "\uFFFF"\
#if {![string length $filepath_or_scriptset]} {
-returnextra 0\
# puts stderr "No filepath_or_scriptset specified"
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_askme [dict get $opts -askme]
set filepath_or_scriptset [dict get $leaders filepath_or_scriptset]
set opt_template [dict get $opts -template]
set opt_askme [dict get $opts -askme]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_template [dict get $opts -template] ;#use dict exists $received -template to see if overridable in .toml
set opt_returnextra [dict get $opts -returnextra]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_force [dict get $opts -force]
set opt_returnextra [dict get $opts -returnextra]
set opt_force [dict get $opts -force]
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set ext [file extension $filepath_or_scriptset]
set ext [file extension $filepath_or_scriptset]
set startdir [pwd]
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
if {[file pathtype $filepath_or_scriptset] ni {absolute relative}} {
error "bad pathtype for '$filepath_or_scriptset' (expected absolute or relative path, or name of scriptset)"
}
#first check if absolute path matches a file or relative path from cwd matches a file
#first check if relative or absolute path matches a file
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
set specified_path $filepath_or_scriptset
set specified_path $filepath_or_scriptset
} else {
} else {
set specified_path [file join $startdir $filepath_or_scriptset]
set specified_path [file join $startdir $filepath_or_scriptset]
}
}
set scriptdir [file dirname $specified_path]
set ext [string trim [file extension $filepath_or_scriptset] .]
set ext [string trim [file extension $filepath_or_scriptset] .]
set allowed_extensions [list wrapconfig tcl ps1 sh bash pl]
set scriptset ""
set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
if {$ext eq ""} {
#set allowed_extensions [list tcl]
set scriptset [file rootname [file tail $specified_path]]
set found_script 0
} elseif {$ext eq "toml"} {
if {[file exists $specified_path]} {
set tomltail [file tail $specified_path]
set found_script 1
if {[string match *_wrap.toml $tomltail]} {
set scriptset [lindex [split $tomltail _] 0]
#if .toml was specified - the config file must exist
if {![file exists $specified_path]} {
if {[file pathtype $filepath_or_scriptset] eq "relative"} {
puts stderr "unable to locate '$specified_path' - will continue search in src/scriptapps folder"
} else {
#caller was specific about path - no fallback to src/scriptapps
error "unable to locate '$specified_path'"
}
}
} else {
error "supplied toml file must be of form <scriptset>_wrap.toml"
}
} else {
} else {
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {$ext ni $allowed_extensions} {
if {[file exists $filepath_or_scriptset.$e]} {
error "supplied filepath_or_scriptset must be the name of a scriptset without extension, a file named <scriptset>_wrap.toml, or a script with one of the extensions: $allowed_extensions"
set found_script 1
}
break
}
set list_input_files [list]
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
puts stderr "No input script files defined in {$scriptset}_wrap.toml"
return false
}
} else {
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
puts stderr "$scriptset.$e"
}
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {[file exists $scriptdir/$scriptset.$e]} {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
}
}
}
} else {
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
}
}
set found_script [expr {[llength $list_input_files] > 0}]
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
set scriptset [file rootname [file tail $specified_path]]
if {$found_script} {
if {$found_script} {
if {[file type $specified_path] eq "file"} {
#found scripts at absolute path - or path relative to cwd
set specified_root [file dirname $specified_path]
set scriptroot $scriptdir
set pathinfo [punk::repo::find_repos [file dirname $specified_path]]
set pathinfo [punk::repo::find_repos $scriptroot]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
if {[string length $projectroot]} {
if {[string length $projectroot]} {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
set customwrapper_folder $projectroot/src/scriptapps/wrappers
set scriptroot [file dirname $specified_path]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
}
} else {
} else {
#outside of any project
#outside of any project
set scriptroot [file dirname $specified_path]
set customwrapper_folder ""
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#no customwrapper folder available
set customwrapper_folder ""
}
}
}
} else {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path."
puts stderr $usage
return false
}
}
} else {
} else {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
return false
}
set pathinfo [punk::repo::find_repos $startdir]
set pathinfo [punk::repo::find_repos $startdir]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[string length $projectroot]} {
if {![string length $projectroot]} {
if {[llength [file split $filepath_or_scriptset]] > 1} {
puts stderr "No matching scripts or config found for $filepath_or_scriptset, and you are not within a directory where projectroot and src/scriptapps can be determined"
puts stderr "filepath_or_scriptset looks like a path - but doesn't seem to point to a file"
return false
puts stderr "Ensure you are within a project and use just the name of the scriptset, or pass in the full correct path or relative path to current directory"
}
puts stderr $usage
return false
set scriptroot $projectroot/src/scriptapps
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#we've already ruled out empty string - so must have a single element representing scriptset - possibly with file extension
#check something matches the scriptset..
set scriptroot $projectroot/src/scriptapps
if {$scriptset ne ""} {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#.toml file may or may not exist
#check something matches the scriptset..
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
set something_found ""
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
if {[file exists $scriptroot/$scriptset]} {
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set found_script 1
if {[dict exists $configd scripts]} {
set something_found $scriptroot/$scriptset ;#extensionless file - that's ok too
set configured_scripts [dict get $configd scripts]
puts stderr "filepath_or_scriptset parameter doesn't seem to refer to a file, and you are not within a directory where projectroot and src/scriptapps/wrappers can be determined"
#expect a single script
puts stderr $usage
if {[file exists $scriptroot/$filepath_or_scriptset]} {
return false
if {[file type $scriptroot/$filepath_or_scriptset] ne "file"} {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path. path: $scriptroot/$filepath_or_scriptset"
if {$process_extensions eq "ALLFOUNDORCONFIGURED"} {
#todo
#todo - look for .wrapconfig or all extensions for the scriptset
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
puts stderr "Sorry - only single input file supported. Supply a file extension or use a .wrapconfig with a single input file for now - implementation incomplete"
#- comparing namespaces_before vs namespaces_after only works if the package was not previously loaded
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
#we could either go to the somewhat expensive route of steaming up an interp with the same auto_path & tcl::tm::list each time..
if {![tcl::namespace::exists $pkg_or_existing_ns]} {
#or cache the result of the namespace we picked for later pkguse calls (pkguse_package_to_namespace dict)
set ver [package require $pkg_unqualified]
#we are using the cache method - but this also doesn't help for packages previously loaded by normal package require
} else {
#our aim is for pkguse <pkgname> to be deterministic in what namespace it finds - even if it doesn't always get the ideal one (e.g cookiejar, see below)
set ver ""
#To determine appropriate namespace for already loaded packages where we have no cache entry - we may still need the helper interp mechanism
}
#The helper interp could be persistent - but only so long as the auto_path/tcl::tm::list values are in sync
#review.
#also see img::png img::raw etc
#these don't directly load namespaces or direct commands.. just change behaviour of existing commands?
#but they can load things like tk (ttk namespace) first one creates ::tkimg?
if {[string match ::* $pkg_or_existing_ns] && [tcl::namespace::exists $pkg_or_existing_ns]} {
#pkguse on an existing full qualified namespace does no package require
set ns $pkg_or_existing_ns
set ns $pkg_or_existing_ns
set ver ""
} else {
} else {
set pkg_unqualified $pkg_or_existing_ns
if {[string match ::* $pkg_or_existing_ns]} {
set ver [package require $pkg_unqualified]
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
set ns ::$pkg_unqualified
} else {
}
set pkg_unqualified $pkg_or_existing_ns
#some packages don't create their namespace immediately and/or don't populate it with commands and instead put entries in ::auto_index
}
set previous_command_count 0
if {[namespace exists $ns]} {
set previous_command_count [llength [info commands ${ns}::*]]
}
#foreach equiv of while 1 - just to allow early exit with break
foreach code_block single {
if {[dict exists $pkguse_package_to_namespace $pkg_unqualified]} {
set ns [dict get $pkguse_package_to_namespace $pkg_unqualified]
set ver [package provide $pkg_unqualified]
break
}
if {[package provide $pkg_unqualified] ne ""} {
#package has already been loaded
if {[namespace exists ::$pkg_unqualified]} {
set ns ::$pkg_unqualified
set ver [package provide $pkg_unqualified]
dict set pkguse_package_to_namespace $pkg_unqualified $ns
break
}
#existing package but no matching namespace..
#- load in throwaway interp and see what cmds/namespaces created
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
}
}
return
return
}
}
#A batch file with unix line-endings is sensitive to label positioning.
#A batch file with unix line-endings is sensitive to label positioning.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#scriptset name to substiture multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
proc multishell {filepath_or_scriptset args} {
#set usage ""
set opts [dict create\
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
-askme 1\
#append usage "The scriptset name will be used to search for <scriptsetname>.sh|.tcl|.ps1 or names as you specify in yourname.wrapconfig if it exists" \n
-outputfolder "\uFFFF"\
#append usage "If no template is specified in a .wrapconfig and no -template argument is supplied, it will default to punk-multishell.cmd" \n
-template "\uFFFF"\
#if {![string length $filepath_or_scriptset]} {
-returnextra 0\
# puts stderr "No filepath_or_scriptset specified"
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_askme [dict get $opts -askme]
set filepath_or_scriptset [dict get $leaders filepath_or_scriptset]
set opt_template [dict get $opts -template]
set opt_askme [dict get $opts -askme]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_template [dict get $opts -template] ;#use dict exists $received -template to see if overridable in .toml
set opt_returnextra [dict get $opts -returnextra]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_force [dict get $opts -force]
set opt_returnextra [dict get $opts -returnextra]
set opt_force [dict get $opts -force]
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set ext [file extension $filepath_or_scriptset]
set ext [file extension $filepath_or_scriptset]
set startdir [pwd]
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
if {[file pathtype $filepath_or_scriptset] ni {absolute relative}} {
error "bad pathtype for '$filepath_or_scriptset' (expected absolute or relative path, or name of scriptset)"
}
#first check if absolute path matches a file or relative path from cwd matches a file
#first check if relative or absolute path matches a file
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
set specified_path $filepath_or_scriptset
set specified_path $filepath_or_scriptset
} else {
} else {
set specified_path [file join $startdir $filepath_or_scriptset]
set specified_path [file join $startdir $filepath_or_scriptset]
}
}
set scriptdir [file dirname $specified_path]
set ext [string trim [file extension $filepath_or_scriptset] .]
set ext [string trim [file extension $filepath_or_scriptset] .]
set allowed_extensions [list wrapconfig tcl ps1 sh bash pl]
set scriptset ""
set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
if {$ext eq ""} {
#set allowed_extensions [list tcl]
set scriptset [file rootname [file tail $specified_path]]
set found_script 0
} elseif {$ext eq "toml"} {
if {[file exists $specified_path]} {
set tomltail [file tail $specified_path]
set found_script 1
if {[string match *_wrap.toml $tomltail]} {
set scriptset [lindex [split $tomltail _] 0]
#if .toml was specified - the config file must exist
if {![file exists $specified_path]} {
if {[file pathtype $filepath_or_scriptset] eq "relative"} {
puts stderr "unable to locate '$specified_path' - will continue search in src/scriptapps folder"
} else {
#caller was specific about path - no fallback to src/scriptapps
error "unable to locate '$specified_path'"
}
}
} else {
error "supplied toml file must be of form <scriptset>_wrap.toml"
}
} else {
} else {
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {$ext ni $allowed_extensions} {
if {[file exists $filepath_or_scriptset.$e]} {
error "supplied filepath_or_scriptset must be the name of a scriptset without extension, a file named <scriptset>_wrap.toml, or a script with one of the extensions: $allowed_extensions"
set found_script 1
}
break
}
set list_input_files [list]
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
puts stderr "No input script files defined in {$scriptset}_wrap.toml"
return false
}
} else {
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
puts stderr "$scriptset.$e"
}
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {[file exists $scriptdir/$scriptset.$e]} {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
}
}
}
} else {
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
}
}
set found_script [expr {[llength $list_input_files] > 0}]
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
set scriptset [file rootname [file tail $specified_path]]
if {$found_script} {
if {$found_script} {
if {[file type $specified_path] eq "file"} {
#found scripts at absolute path - or path relative to cwd
set specified_root [file dirname $specified_path]
set scriptroot $scriptdir
set pathinfo [punk::repo::find_repos [file dirname $specified_path]]
set pathinfo [punk::repo::find_repos $scriptroot]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
if {[string length $projectroot]} {
if {[string length $projectroot]} {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
set customwrapper_folder $projectroot/src/scriptapps/wrappers
set scriptroot [file dirname $specified_path]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
}
} else {
} else {
#outside of any project
#outside of any project
set scriptroot [file dirname $specified_path]
set customwrapper_folder ""
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#no customwrapper folder available
set customwrapper_folder ""
}
}
}
} else {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path."
puts stderr $usage
return false
}
}
} else {
} else {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
return false
}
set pathinfo [punk::repo::find_repos $startdir]
set pathinfo [punk::repo::find_repos $startdir]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[string length $projectroot]} {
if {![string length $projectroot]} {
if {[llength [file split $filepath_or_scriptset]] > 1} {
puts stderr "No matching scripts or config found for $filepath_or_scriptset, and you are not within a directory where projectroot and src/scriptapps can be determined"
puts stderr "filepath_or_scriptset looks like a path - but doesn't seem to point to a file"
return false
puts stderr "Ensure you are within a project and use just the name of the scriptset, or pass in the full correct path or relative path to current directory"
}
puts stderr $usage
return false
set scriptroot $projectroot/src/scriptapps
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#we've already ruled out empty string - so must have a single element representing scriptset - possibly with file extension
#check something matches the scriptset..
set scriptroot $projectroot/src/scriptapps
if {$scriptset ne ""} {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#.toml file may or may not exist
#check something matches the scriptset..
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
set something_found ""
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
if {[file exists $scriptroot/$scriptset]} {
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set found_script 1
if {[dict exists $configd scripts]} {
set something_found $scriptroot/$scriptset ;#extensionless file - that's ok too
set configured_scripts [dict get $configd scripts]
puts stderr "filepath_or_scriptset parameter doesn't seem to refer to a file, and you are not within a directory where projectroot and src/scriptapps/wrappers can be determined"
#expect a single script
puts stderr $usage
if {[file exists $scriptroot/$filepath_or_scriptset]} {
return false
if {[file type $scriptroot/$filepath_or_scriptset] ne "file"} {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path. path: $scriptroot/$filepath_or_scriptset"
if {$process_extensions eq "ALLFOUNDORCONFIGURED"} {
#todo
#todo - look for .wrapconfig or all extensions for the scriptset
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
puts stderr "Sorry - only single input file supported. Supply a file extension or use a .wrapconfig with a single input file for now - implementation incomplete"
#- comparing namespaces_before vs namespaces_after only works if the package was not previously loaded
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
#we could either go to the somewhat expensive route of steaming up an interp with the same auto_path & tcl::tm::list each time..
if {![tcl::namespace::exists $pkg_or_existing_ns]} {
#or cache the result of the namespace we picked for later pkguse calls (pkguse_package_to_namespace dict)
set ver [package require $pkg_unqualified]
#we are using the cache method - but this also doesn't help for packages previously loaded by normal package require
} else {
#our aim is for pkguse <pkgname> to be deterministic in what namespace it finds - even if it doesn't always get the ideal one (e.g cookiejar, see below)
set ver ""
#To determine appropriate namespace for already loaded packages where we have no cache entry - we may still need the helper interp mechanism
}
#The helper interp could be persistent - but only so long as the auto_path/tcl::tm::list values are in sync
#review.
#also see img::png img::raw etc
#these don't directly load namespaces or direct commands.. just change behaviour of existing commands?
#but they can load things like tk (ttk namespace) first one creates ::tkimg?
if {[string match ::* $pkg_or_existing_ns] && [tcl::namespace::exists $pkg_or_existing_ns]} {
#pkguse on an existing full qualified namespace does no package require
set ns $pkg_or_existing_ns
set ns $pkg_or_existing_ns
set ver ""
} else {
} else {
set pkg_unqualified $pkg_or_existing_ns
if {[string match ::* $pkg_or_existing_ns]} {
set ver [package require $pkg_unqualified]
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
set ns ::$pkg_unqualified
} else {
}
set pkg_unqualified $pkg_or_existing_ns
#some packages don't create their namespace immediately and/or don't populate it with commands and instead put entries in ::auto_index
}
set previous_command_count 0
if {[namespace exists $ns]} {
set previous_command_count [llength [info commands ${ns}::*]]
}
#foreach equiv of while 1 - just to allow early exit with break
foreach code_block single {
if {[dict exists $pkguse_package_to_namespace $pkg_unqualified]} {
set ns [dict get $pkguse_package_to_namespace $pkg_unqualified]
set ver [package provide $pkg_unqualified]
break
}
if {[package provide $pkg_unqualified] ne ""} {
#package has already been loaded
if {[namespace exists ::$pkg_unqualified]} {
set ns ::$pkg_unqualified
set ver [package provide $pkg_unqualified]
dict set pkguse_package_to_namespace $pkg_unqualified $ns
break
}
#existing package but no matching namespace..
#- load in throwaway interp and see what cmds/namespaces created
@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate.
@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system
@REM e.g from within a running punkshell: deck scriptwrap.multishell <inputfilepath> -outputfolder <folderpath>
@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used)
@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called.
@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only.
@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays.
@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context
@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch
@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx
@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set
@REM If more than 32 chars needed for a target, it can still be done but overall script padding may need checking/adjusting
@REM Supporting more explicit oses than those listed may also require script padding adjustment
@rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable).
:<asadmin>
@SET"asadmin=0"
:</asadmin>
@REM @ECHO nextshelltype is %nextshelltype[win32___________]%
@REM -- cmd/batch file section (ignored on unix but should be left in place)
@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary)
@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone.
@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888
@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly.
@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133
@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway)
@REM -- Even something as simple as adding or removing an @REM
@REM -- From within punkshell - use:
@REM -- deck scriptwrap.checkfile <filepath>
@REM -- to check your templates or final wrapped scripts for byte boundary issues
@REM -- It will report any labels that are on boundaries
@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets.
@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended.
@REM -- Alternatively, as you should do anyway - test the final script on windows
@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations.
@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here.
@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label
@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries.
@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
put stderr "commandset::scriptwrap::templates_dict WARNING - no handler available for the 'punk.templates' capability - template providers will be unable to provide template locations"
}
}
return
return
}
}
#A batch file with unix line-endings is sensitive to label positioning.
#A batch file with unix line-endings is sensitive to label positioning.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#batch file with windows crlf line endings can exhibit this problem - but probably only if specifically crafted with long lines deliberately designed to trigger it.
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#see: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 (Call and goto may fail when the batch file has Unix line endings)
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#specific filepath to just wrap one script at the xxx-pre-launch-suprocess site
#scriptset name to substiture multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
#scriptset name to substitute multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf
proc multishell {filepath_or_scriptset args} {
#set usage ""
set opts [dict create\
#append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n
-askme 1\
#append usage "The scriptset name will be used to search for <scriptsetname>.sh|.tcl|.ps1 or names as you specify in yourname.wrapconfig if it exists" \n
-outputfolder "\uFFFF"\
#append usage "If no template is specified in a .wrapconfig and no -template argument is supplied, it will default to punk-multishell.cmd" \n
-template "\uFFFF"\
#if {![string length $filepath_or_scriptset]} {
-returnextra 0\
# puts stderr "No filepath_or_scriptset specified"
set argd [punk::args::parse $args withid ::punk::mix::commandset::scriptwrap::multishell]
lassign [dict values $argd] leaders opts values received
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set opt_askme [dict get $opts -askme]
set filepath_or_scriptset [dict get $leaders filepath_or_scriptset]
set opt_template [dict get $opts -template]
set opt_askme [dict get $opts -askme]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_template [dict get $opts -template] ;#use dict exists $received -template to see if overridable in .toml
set opt_returnextra [dict get $opts -returnextra]
set opt_outputfolder [dict get $opts -outputfolder]
set opt_force [dict get $opts -force]
set opt_returnextra [dict get $opts -returnextra]
set opt_force [dict get $opts -force]
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- ---
set ext [file extension $filepath_or_scriptset]
set ext [file extension $filepath_or_scriptset]
set startdir [pwd]
set startdir [pwd]
set allowed_extensions [list tcl ps1 sh bash pl]
#TODO - distinct sections for sh vs bash? needs experiments..
#for now we use shell-pre-launch-subprocess etc
#set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
set extension_langs [list tcl tcl ps1 powershell sh shell bash shell pl perl]
if {[file pathtype $filepath_or_scriptset] ni {absolute relative}} {
error "bad pathtype for '$filepath_or_scriptset' (expected absolute or relative path, or name of scriptset)"
}
#first check if absolute path matches a file or relative path from cwd matches a file
#first check if relative or absolute path matches a file
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
set specified_path $filepath_or_scriptset
set specified_path $filepath_or_scriptset
} else {
} else {
set specified_path [file join $startdir $filepath_or_scriptset]
set specified_path [file join $startdir $filepath_or_scriptset]
}
}
set scriptdir [file dirname $specified_path]
set ext [string trim [file extension $filepath_or_scriptset] .]
set ext [string trim [file extension $filepath_or_scriptset] .]
set allowed_extensions [list wrapconfig tcl ps1 sh bash pl]
set scriptset ""
set extension_langs [list tcl tcl ps1 powershell sh sh bash bash pl perl]
if {$ext eq ""} {
#set allowed_extensions [list tcl]
set scriptset [file rootname [file tail $specified_path]]
set found_script 0
} elseif {$ext eq "toml"} {
if {[file exists $specified_path]} {
set tomltail [file tail $specified_path]
set found_script 1
if {[string match *_wrap.toml $tomltail]} {
set scriptset [lindex [split $tomltail _] 0]
#if .toml was specified - the config file must exist
if {![file exists $specified_path]} {
if {[file pathtype $filepath_or_scriptset] eq "relative"} {
puts stderr "unable to locate '$specified_path' - will continue search in src/scriptapps folder"
} else {
#caller was specific about path - no fallback to src/scriptapps
error "unable to locate '$specified_path'"
}
}
} else {
error "supplied toml file must be of form <scriptset>_wrap.toml"
}
} else {
} else {
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {$ext ni $allowed_extensions} {
if {[file exists $filepath_or_scriptset.$e]} {
error "supplied filepath_or_scriptset must be the name of a scriptset without extension, a file named <scriptset>_wrap.toml, or a script with one of the extensions: $allowed_extensions"
set found_script 1
}
break
}
set list_input_files [list]
set configd [dict create]
if {$scriptset ne ""} {
puts stdout "Attempting to process all scripts belonging to scriptset '$scriptset'"
#.toml file may or may not exist
if {[file exists ${scriptset}_wrap.toml]} {
puts stdout "Loading configuration from $scriptdir/${scriptset}_wrap.toml"
set configd [_read_scriptset_wrap_tomlfile $scriptdir/${scriptset}_wrap.toml]
if {[dict exists $configd scripts]} {
set configured_scripts [dict get $configd scripts]
puts stderr "No input script files defined in {$scriptset}_wrap.toml"
return false
}
} else {
puts stdout "No config file for scriptset (must be named ${scriptset}_wrap.toml"
puts stdout "Will look for the following scripts in $scriptdir"
foreach e $allowed_extensions {
puts stderr "$scriptset.$e"
}
foreach e [concat $allowed_extensions [string toupper $allowed_extensions]] {
if {[file exists $scriptdir/$scriptset.$e]} {
lappend list_input_files $scriptdir/$scriptset.$e
}
}
}
}
}
} else {
#expect a single script
if {[file exists $specified_path]} {
lappend list_input_files $specified_path
}
}
}
set found_script [expr {[llength $list_input_files] > 0}]
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
#TODO! - use get_wrapper_folders - multishell should use same available templates as the 'templates' function
set scriptset [file rootname [file tail $specified_path]]
if {$found_script} {
if {$found_script} {
if {[file type $specified_path] eq "file"} {
#found scripts at absolute path - or path relative to cwd
set specified_root [file dirname $specified_path]
set scriptroot $scriptdir
set pathinfo [punk::repo::find_repos [file dirname $specified_path]]
set pathinfo [punk::repo::find_repos $scriptroot]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
if {[string length $projectroot]} {
if {[string length $projectroot]} {
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder
set customwrapper_folder $projectroot/src/scriptapps/wrappers
set scriptroot [file dirname $specified_path]
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
}
} else {
} else {
#outside of any project
#outside of any project
set scriptroot [file dirname $specified_path]
set customwrapper_folder ""
if {[file exists $scriptroot/wrappers]} {
set customwrapper_folder $scriptroot/wrappers
} else {
#no customwrapper folder available
set customwrapper_folder ""
}
}
}
} else {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path."
puts stderr $usage
return false
}
}
} else {
} else {
if {[file pathtype $filepath_or_scriptset] eq "absolute"} {
return false
}
set pathinfo [punk::repo::find_repos $startdir]
set pathinfo [punk::repo::find_repos $startdir]
set projectroot [dict get $pathinfo closest]
set projectroot [dict get $pathinfo closest]
if {[string length $projectroot]} {
if {![string length $projectroot]} {
if {[llength [file split $filepath_or_scriptset]] > 1} {
puts stderr "No matching scripts or config found for $filepath_or_scriptset, and you are not within a directory where projectroot and src/scriptapps can be determined"
puts stderr "filepath_or_scriptset looks like a path - but doesn't seem to point to a file"
return false
puts stderr "Ensure you are within a project and use just the name of the scriptset, or pass in the full correct path or relative path to current directory"
}
puts stderr $usage
return false
set scriptroot $projectroot/src/scriptapps
} else {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#we've already ruled out empty string - so must have a single element representing scriptset - possibly with file extension
#check something matches the scriptset..
set scriptroot $projectroot/src/scriptapps
if {$scriptset ne ""} {
set customwrapper_folder $projectroot/src/scriptapps/wrappers
#.toml file may or may not exist
#check something matches the scriptset..
if {[file exists $scriptroot/${scriptset}_wrap.toml]} {
set something_found ""
puts stdout "Loading configuration from $scriptroot/${scriptset}_wrap.toml"
if {[file exists $scriptroot/$scriptset]} {
set configd [_read_scriptset_wrap_tomlfile $scriptroot/${scriptset}_wrap.toml]
set found_script 1
if {[dict exists $configd scripts]} {
set something_found $scriptroot/$scriptset ;#extensionless file - that's ok too
set configured_scripts [dict get $configd scripts]
puts stderr "filepath_or_scriptset parameter doesn't seem to refer to a file, and you are not within a directory where projectroot and src/scriptapps/wrappers can be determined"
#expect a single script
puts stderr $usage
if {[file exists $scriptroot/$filepath_or_scriptset]} {
return false
if {[file type $scriptroot/$filepath_or_scriptset] ne "file"} {
puts stderr "wrap_in_multishell doesn't currently support a directory as the path. path: $scriptroot/$filepath_or_scriptset"
if {$process_extensions eq "ALLFOUNDORCONFIGURED"} {
#todo
#todo - look for .wrapconfig or all extensions for the scriptset
puts stderr "Sorry - only single input file supported. Supply a file extension or use a <scriptset>_wrap.toml config with a single input file for now - implementation incomplete"
puts stderr "Sorry - only single input file supported. Supply a file extension or use a .wrapconfig with a single input file for now - implementation incomplete"
#- comparing namespaces_before vs namespaces_after only works if the package was not previously loaded
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
#we could either go to the somewhat expensive route of steaming up an interp with the same auto_path & tcl::tm::list each time..
if {![tcl::namespace::exists $pkg_or_existing_ns]} {
#or cache the result of the namespace we picked for later pkguse calls (pkguse_package_to_namespace dict)
set ver [package require $pkg_unqualified]
#we are using the cache method - but this also doesn't help for packages previously loaded by normal package require
} else {
#our aim is for pkguse <pkgname> to be deterministic in what namespace it finds - even if it doesn't always get the ideal one (e.g cookiejar, see below)
set ver ""
#To determine appropriate namespace for already loaded packages where we have no cache entry - we may still need the helper interp mechanism
}
#The helper interp could be persistent - but only so long as the auto_path/tcl::tm::list values are in sync
#review.
#also see img::png img::raw etc
#these don't directly load namespaces or direct commands.. just change behaviour of existing commands?
#but they can load things like tk (ttk namespace) first one creates ::tkimg?
if {[string match ::* $pkg_or_existing_ns] && [tcl::namespace::exists $pkg_or_existing_ns]} {
#pkguse on an existing full qualified namespace does no package require
set ns $pkg_or_existing_ns
set ns $pkg_or_existing_ns
set ver ""
} else {
} else {
set pkg_unqualified $pkg_or_existing_ns
if {[string match ::* $pkg_or_existing_ns]} {
set ver [package require $pkg_unqualified]
set pkg_unqualified [string range $pkg_or_existing_ns 2 end]
set ns ::$pkg_unqualified
} else {
}
set pkg_unqualified $pkg_or_existing_ns
#some packages don't create their namespace immediately and/or don't populate it with commands and instead put entries in ::auto_index
}
set previous_command_count 0
if {[namespace exists $ns]} {
set previous_command_count [llength [info commands ${ns}::*]]
}
#foreach equiv of while 1 - just to allow early exit with break
foreach code_block single {
if {[dict exists $pkguse_package_to_namespace $pkg_unqualified]} {
set ns [dict get $pkguse_package_to_namespace $pkg_unqualified]
set ver [package provide $pkg_unqualified]
break
}
if {[package provide $pkg_unqualified] ne ""} {
#package has already been loaded
if {[namespace exists ::$pkg_unqualified]} {
set ns ::$pkg_unqualified
set ver [package provide $pkg_unqualified]
dict set pkguse_package_to_namespace $pkg_unqualified $ns
break
}
#existing package but no matching namespace..
#- load in throwaway interp and see what cmds/namespaces created