Julian Noble
1 year ago
71 changed files with 9804 additions and 2929 deletions
@ -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 |
@ -0,0 +1,7 @@ |
|||||||
|
src |
||||||
|
src/vendorlib |
||||||
|
src/vendormodules |
||||||
|
src/modules |
||||||
|
src/lib |
||||||
|
lib |
||||||
|
modules |
@ -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 |
@ -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 |
@ -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 <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application 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> $cmd] { |
||||||
|
::puts stderr "CANARYSPACE <cmd>" |
||||||
|
::puts stderr " [::info level 0]" |
||||||
|
::tailcall ::<cmd> {*}$args |
||||||
|
} ] |
||||||
|
} |
||||||
|
} |
||||||
|
build_commands |
||||||
|
} |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval canaryspace { |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide canaryspace [namespace eval canaryspace { |
||||||
|
::variable version |
||||||
|
::set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,789 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::cli 0.3 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
package require punk::repo |
||||||
|
package require punkcheck ;#checksum and/or timestamp records |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
|
||||||
|
namespace eval punk::mix::cli { |
||||||
|
namespace eval temp_import { |
||||||
|
} |
||||||
|
namespace ensemble create |
||||||
|
|
||||||
|
package require punk::overlay |
||||||
|
catch { |
||||||
|
punk::overlay::import_commandset module. ::punk::mix::commandset::module |
||||||
|
} |
||||||
|
punk::overlay::import_commandset debug. ::punk::mix::commandset::debug |
||||||
|
punk::overlay::import_commandset repo. ::punk::mix::commandset::repo |
||||||
|
punk::overlay::import_commandset lib. ::punk::mix::commandset::loadedlib |
||||||
|
|
||||||
|
catch { |
||||||
|
package require punk::mix::commandset::project |
||||||
|
punk::overlay::import_commandset project. ::punk::mix::commandset::project |
||||||
|
punk::overlay::import_commandset "" ::punk::mix::commandset::project::collection |
||||||
|
} |
||||||
|
if {[catch { |
||||||
|
package require punk::mix::commandset::layout |
||||||
|
punk::overlay::import_commandset project.layout. ::punk::mix::commandset::layout |
||||||
|
punk::overlay::import_commandset "project." ::punk::mix::commandset::layout::collection |
||||||
|
} errM]} { |
||||||
|
puts stderr "error loading punk::mix::commandset::layout" |
||||||
|
puts stderr $errM |
||||||
|
} |
||||||
|
if {[catch { |
||||||
|
package require punk::mix::commandset::buildsuite |
||||||
|
punk::overlay::import_commandset buildsuite. ::punk::mix::commandset::buildsuite |
||||||
|
punk::overlay::import_commandset "" ::punk::mix::commandset::buildsuite::collection |
||||||
|
} errM]} { |
||||||
|
puts stderr "error loading punk::mix::commandset::buildsuite" |
||||||
|
puts stderr $errM |
||||||
|
} |
||||||
|
punk::overlay::import_commandset scriptwrap. ::punk::mix::commandset::scriptwrap |
||||||
|
|
||||||
|
|
||||||
|
proc help {args} { |
||||||
|
#set basehelp [punk::mix::base::help -extension [namespace current] {*}$args] |
||||||
|
set basehelp [punk::mix::base help {*}$args] |
||||||
|
#puts stdout "punk::mix help" |
||||||
|
return $basehelp |
||||||
|
} |
||||||
|
|
||||||
|
proc stat {{workingdir ""} args} { |
||||||
|
dict set args -v 0 |
||||||
|
punk::mix::cli::lib::get_status $workingdir {*}$args |
||||||
|
} |
||||||
|
proc status {{workingdir ""} args} { |
||||||
|
dict set args -v 1 |
||||||
|
punk::mix::cli::lib::get_status $workingdir {*}$args |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace eval punk::mix::cli { |
||||||
|
|
||||||
|
|
||||||
|
#interp alias {} ::punk::mix::cli::project.new {} ::punk::mix::cli::new |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
proc make {args} { |
||||||
|
set startdir [pwd] |
||||||
|
set project_base "" ;#empty for unknown |
||||||
|
if {[punk::repo::is_git $startdir]} { |
||||||
|
set project_base [punk::repo::find_git] |
||||||
|
set sourcefolder $project_base/src |
||||||
|
} elseif {[punk::repo::is_fossil $startdir]} { |
||||||
|
set project_base [punk::repo::find_fossil] |
||||||
|
set sourcefolder $project_base/src |
||||||
|
} else { |
||||||
|
if {[punk::repo::is_candidate $startdir]} { |
||||||
|
set project_base [punk::repo::find_candidate] |
||||||
|
set sourcefolder $project_base/src |
||||||
|
puts stderr "WARNING - project not under git or fossil control" |
||||||
|
puts stderr "Using base folder $project_base" |
||||||
|
} else { |
||||||
|
set sourcefolder $startdir |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#review - why can't we be anywhere in the project? |
||||||
|
if {([file tail $sourcefolder] ne "src") || (![file exists $sourcefolder/make.tcl])} { |
||||||
|
puts stderr "pmix make must be run from src folder containing make.tcl - unable to proceed (cwd: [pwd])" |
||||||
|
if {[string length $project_base]} { |
||||||
|
if {[file exists $project_base/src] && [string tolower [pwd]] ne [string tolower $project_base/src]} { |
||||||
|
puts stderr "Try cd to $project_base/src" |
||||||
|
} |
||||||
|
} else { |
||||||
|
if {[file exists $startdir/Makefile]} { |
||||||
|
puts stdout "A Makefile exists at $startdir/Makefile." |
||||||
|
if {"windows" eq $::tcl_platform(platform)} { |
||||||
|
puts stdout "Try running: msys2 -ucrt64 -here -c \"make build\" or bash -c \"make build\"" |
||||||
|
} else { |
||||||
|
puts stdout "Try runing: make build" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
if {![string length $project_base]} { |
||||||
|
puts stderr "WARNING no git or fossil repository detected." |
||||||
|
puts stderr "Using base folder $startdir" |
||||||
|
set project_base $startdir |
||||||
|
} |
||||||
|
|
||||||
|
set lc_this_exe [string tolower [info nameofexecutable]] |
||||||
|
set lc_proj_bin [string tolower $project_base/bin] |
||||||
|
set lc_build_bin [string tolower $project_base/src/_build] |
||||||
|
|
||||||
|
|
||||||
|
set is_own_exe 0 |
||||||
|
if {[string match "${lc_proj_bin}*" $lc_this_exe] || [string match "${lc_build_bin}" $lc_this_exe]} { |
||||||
|
set is_own_exe 1 |
||||||
|
puts stderr "WARNING - running make using executable that may be created by the project being built" |
||||||
|
set answer [util::askuser "Do you want to proceed using this executable? (build will probably stop when it is unable to update the executable) Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
cd $sourcefolder |
||||||
|
#use run so that stdout visible as it goes |
||||||
|
set exitinfo [run [info nameofexecutable] $sourcefolder/make.tcl project] |
||||||
|
set exitcode [dict get $exitinfo exitcode] |
||||||
|
|
||||||
|
cd $startdir |
||||||
|
if {$exitcode != 0} { |
||||||
|
puts stderr "FAILED with exitcode $exitcode" |
||||||
|
return false |
||||||
|
} else { |
||||||
|
puts stdout "OK make finished " |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc Kettle {args} { |
||||||
|
tailcall lib::kettle_call lib {*}$args |
||||||
|
} |
||||||
|
proc KettleShell {args} { |
||||||
|
tailcall lib::kettle_call shell {*}$args |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace eval lib { |
||||||
|
namespace path ::punk::mix::util |
||||||
|
|
||||||
|
|
||||||
|
proc module_types {} { |
||||||
|
#first in list is default for unspecified -type when creating new module |
||||||
|
return [list plain tarjar zipkit] |
||||||
|
} |
||||||
|
|
||||||
|
proc validate_modulename {modulename args} { |
||||||
|
set defaults [list\ |
||||||
|
-name_description modulename\ |
||||||
|
] |
||||||
|
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} |
||||||
|
set known_opts [dict keys $defaults] |
||||||
|
foreach k [dict keys $args] { |
||||||
|
if {$k ni $known_opts} { |
||||||
|
error "validate_modulename error: unknown option $k. known options: $known_opts" |
||||||
|
} |
||||||
|
} |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_name_description [dict get $opts -name_description] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
|
||||||
|
validate_name_not_empty_or_spaced $modulename -name_description $opt_name_description |
||||||
|
set testname [string map [list :: ""] $modulename] |
||||||
|
if {[string first : $testname] >=0} { |
||||||
|
error "$opt_name_description '$modulename' can only contain paired colons" |
||||||
|
} |
||||||
|
set badchars [list - "$" "?" "*"] |
||||||
|
foreach bc $badchars { |
||||||
|
if {[string first $bc $modulename] >= 0} { |
||||||
|
error "$opt_name_description '$modulename' can not contain character '$bc'" |
||||||
|
} |
||||||
|
} |
||||||
|
return $modulename |
||||||
|
} |
||||||
|
|
||||||
|
proc validate_projectname {projectname args} { |
||||||
|
set defaults [list\ |
||||||
|
-name_description projectname\ |
||||||
|
] |
||||||
|
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} |
||||||
|
set known_opts [dict keys $defaults] |
||||||
|
foreach k [dict keys $args] { |
||||||
|
if {$k ni $known_opts} { |
||||||
|
error "validate_modulename error: unknown option $k. known options: $known_opts" |
||||||
|
} |
||||||
|
} |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_name_description [dict get $opts -name_description] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
validate_name_not_empty_or_spaced $projectname -name_description $opt_name_description |
||||||
|
set reserved_words [list etc lib bin modules src doc vendorlib vendormodules embedded runtime _aside _build] |
||||||
|
if {$projectname in $reserved_words } { |
||||||
|
error "$opt_name_description '$projectname' cannot be one of reserved_words: $reserved_words" |
||||||
|
} |
||||||
|
if {[string first "::" $projectname] >= 0} { |
||||||
|
error "$opt_name_description '$projectname' cannot contain namespace separator '::'" |
||||||
|
} |
||||||
|
return $projectname |
||||||
|
} |
||||||
|
proc validate_name_not_empty_or_spaced {name args} { |
||||||
|
set defaults [list\ |
||||||
|
-name_description projectname\ |
||||||
|
] |
||||||
|
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} |
||||||
|
set known_opts [dict keys $defaults] |
||||||
|
foreach k [dict keys $args] { |
||||||
|
if {$k ni $known_opts} { |
||||||
|
error "validate_modulename error: unknown option $k. known options: $known_opts" |
||||||
|
} |
||||||
|
} |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_name_description [dict get $opts -name_description] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
if {![string length $name]} { |
||||||
|
error "$opt_name_description cannot be empty" |
||||||
|
} |
||||||
|
if {[string length [string map [list " " "" \n "" \r "" \t ""] $name]] != [string length $name]} { |
||||||
|
error "$opt_name_description cannot contain whitespace" |
||||||
|
} |
||||||
|
return $name |
||||||
|
} |
||||||
|
|
||||||
|
#split modulename (as present in a filename or namespaced name) into name/version ignoring leading namespace path |
||||||
|
#ignore trailing .tm .TM if present |
||||||
|
#if version doesn't pass validation - treat it as part of the modulename and return empty version string without error |
||||||
|
#Up to caller to validate. |
||||||
|
proc split_modulename_version {modulename} { |
||||||
|
set lastpart [namespace tail $modulename] |
||||||
|
set lastpart [file tail $lastpart] ;# should be ok to use file tail now that we've ensured no namespace components |
||||||
|
if {[string equal -nocase [file extension $modulename] ".tm"]} { |
||||||
|
set fileparts [split [file rootname $lastpart] -] |
||||||
|
} else { |
||||||
|
set fileparts [split $lastpart -] |
||||||
|
} |
||||||
|
if {[punk::mix::util::is_valid_tm_version [lindex $fileparts end]]} { |
||||||
|
set versionsegment [lindex $fileparts end] |
||||||
|
set namesegment [join [lrange $fileparts 0 end-1] -];#re-stitch |
||||||
|
} else { |
||||||
|
# |
||||||
|
set namesegment [join $fileparts -] |
||||||
|
set versionsegment "" |
||||||
|
} |
||||||
|
return [list $namesegment $versionsegment] |
||||||
|
} |
||||||
|
|
||||||
|
proc get_status {{workingdir ""} args} { |
||||||
|
set result "" |
||||||
|
if {$workingdir ne ""} { |
||||||
|
if {[file pathtype $workingdir] ne "absolute"} { |
||||||
|
set workingdir [file normalize $workingdir] |
||||||
|
} |
||||||
|
set active_dir $workingdir |
||||||
|
} else { |
||||||
|
set active_dir [pwd] |
||||||
|
} |
||||||
|
set defaults [dict create\ |
||||||
|
-v 1\ |
||||||
|
] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- --- --- |
||||||
|
set opt_v [dict get $opts -v] |
||||||
|
# -- --- --- --- --- --- --- --- --- |
||||||
|
|
||||||
|
|
||||||
|
set repopaths [punk::repo::find_repos [pwd]] |
||||||
|
set repos [dict get $repopaths repos] |
||||||
|
if {![llength $repos]} { |
||||||
|
append result [dict get $repopaths warnings] |
||||||
|
} else { |
||||||
|
append result [dict get $repopaths warnings] |
||||||
|
lassign [lindex $repos 0] repopath repotypes |
||||||
|
if {"fossil" in $repotypes} { |
||||||
|
#review - multiple process launches to fossil a bit slow on windows.. |
||||||
|
#could we query global db in one go instead? |
||||||
|
# |
||||||
|
set fossil_prog [auto_execok fossil] |
||||||
|
append result "FOSSIL project based at $repopath with revision: [punk::repo::fossil_revision $repopath]" \n |
||||||
|
set fosinfo [exec {*}$fossil_prog info] |
||||||
|
append result [join [punk::repo::grep {repository:*} $fosinfo] \n] \n |
||||||
|
|
||||||
|
set fosrem [exec {*}$fossil_prog remote ls] |
||||||
|
if {[string length $fosrem]} { |
||||||
|
append result "Remotes:\n" |
||||||
|
append result " " $fosrem \n |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
append result [join [punk::repo::grep {tags:*} $fosinfo] \n] \n |
||||||
|
|
||||||
|
set dbinfo [exec {*}$fossil_prog dbstat] |
||||||
|
append result [join [punk::repo::grep {project-name:*} $dbinfo] \n] \n |
||||||
|
append result [join [punk::repo::grep {tickets:*} $dbinfo] \n] \n |
||||||
|
append result [join [punk::repo::grep {project-age:*} $dbinfo] \n] \n |
||||||
|
append result [join [punk::repo::grep {latest-change:*} $dbinfo] \n] \n |
||||||
|
append result [join [punk::repo::grep {files:*} $dbinfo] \n] \n |
||||||
|
append result [join [punk::repo::grep {check-ins:*} $dbinfo] \n] \n |
||||||
|
if {"project" in $repotypes} { |
||||||
|
#punk project |
||||||
|
if {![catch {package require textblock; package require patternpunk}]} { |
||||||
|
set result [textblock::join [textblock::join [>punk . logo] " "] $result] |
||||||
|
append result \n |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set timeline [exec fossil timeline -n 5 -t ci] |
||||||
|
set timeline [string map [list \r\n \n] $timeline] |
||||||
|
append result $timeline |
||||||
|
if {$opt_v} { |
||||||
|
set repostate [punk::repo::workingdir_state $repopath -repopaths $repopaths -repotypes fossil] |
||||||
|
append result \n [punk::repo::workingdir_state_summary $repostate] |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
#repotypes *could* be both git and fossil - so report both if so |
||||||
|
if {"git" in $repotypes} { |
||||||
|
append result "GIT project based at $repopath with revision: [punk::repo::git_revision $repopath]" \n |
||||||
|
if {[string length [set git_prog [auto_execok git]]]} { |
||||||
|
set git_remotes [exec {*}$git_prog remote -v] |
||||||
|
append result $git_remotes |
||||||
|
if {$opt_v} { |
||||||
|
set repostate [punk::repo::workingdir_state $repopath -repopaths $repopaths -repotypes git] |
||||||
|
append result \n [punk::repo::workingdir_state_summary $repostate] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc build_modules_from_source_to_base {srcdir basedir args} { |
||||||
|
set antidir [list "#*" "_aside" ".git" ".fossil*"] ;#exact or glob patterns for folders we don't want to search in. |
||||||
|
set defaults [list\ |
||||||
|
-installer punk::mix::cli::build_modules_from_source_to_base\ |
||||||
|
-call-depth-internal 0\ |
||||||
|
-max_depth 1000\ |
||||||
|
-subdirlist {}\ |
||||||
|
-punkcheck_eventid "\uFFFF"\ |
||||||
|
-glob *.tm\ |
||||||
|
] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
|
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set installername [dict get $opts -installer] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set CALLDEPTH [dict get $opts -call-depth-internal] |
||||||
|
set max_depth [dict get $opts -max_depth] |
||||||
|
set subdirlist [dict get $opts -subdirlist] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set fileglob [dict get $opts -glob] |
||||||
|
if {![string match "*.tm" $fileglob]} { |
||||||
|
error "build_modules_from_source_to_base -glob '$fileglob' doesn't seem to target tcl modules." |
||||||
|
} |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_punkcheck_eventid [dict get $opts -punkcheck_eventid] |
||||||
|
|
||||||
|
set magicversion [punk::mix::util::magic_tm_version] ;#deliberately large so given load-preference when testing |
||||||
|
set module_list [list] |
||||||
|
|
||||||
|
if {[file tail [file dirname $srcdir]] ne "src"} { |
||||||
|
puts stderr "ERROR build_modules_from_source_to_base can only be called with a srcdir that is a subfolder of your 'src' directory" |
||||||
|
puts stderr "The .tm modules are namespaced based on their directory depth - so we need to start at the root" |
||||||
|
puts stderr "To build a subtree of your modules - use an appropriate src/modules folder and pass in the -subdirlist." |
||||||
|
puts stderr "e.g if your modules are based at /x/src/modules2 and you wish to build only the .tm files at /x/src/modules2/skunkworks/lib" |
||||||
|
puts stderr "Use: >build_modules_from_source_to_base /x/src/modules2 /x/modules2 -subdirlist {skunkworks lib}" |
||||||
|
exit 2 |
||||||
|
} |
||||||
|
set srcdirname [file tail $srcdir] |
||||||
|
|
||||||
|
set build [file dirname $srcdir]/_build/$srcdirname ;#relative to *original* srcdir - not current_source_dir |
||||||
|
if {[llength $subdirlist] == 0} { |
||||||
|
set target_module_dir $basedir |
||||||
|
set current_source_dir $srcdir |
||||||
|
} else { |
||||||
|
set target_module_dir $basedir/[file join {*}$subdirlist] |
||||||
|
set current_source_dir $srcdir/[file join {*}$subdirlist] |
||||||
|
} |
||||||
|
if {![file exists $target_module_dir]} { |
||||||
|
error "build_modules_from_source_to_base from current source dir: '$current_source_dir'. Basedir:'$current_module_dir' doesn't exist or is empty" |
||||||
|
} |
||||||
|
if {![file exists $current_source_dir]} { |
||||||
|
error "build_modules_from_source_to_base from current source dir:'$current_source_dir' doesn't exist or is empty" |
||||||
|
} |
||||||
|
|
||||||
|
#---------------------------------------- |
||||||
|
set punkcheck_file [file join $basedir/.punkcheck] |
||||||
|
if {$CALLDEPTH == 0} { |
||||||
|
|
||||||
|
set config [dict create\ |
||||||
|
-glob $fileglob\ |
||||||
|
-max_depth 0\ |
||||||
|
] |
||||||
|
lassign [punkcheck::start_installer_event $punkcheck_file $installername $srcdir $basedir $config] _eventid punkcheck_eventid _recordset record_list |
||||||
|
|
||||||
|
} else { |
||||||
|
set punkcheck_eventid $opt_punkcheck_eventid |
||||||
|
} |
||||||
|
#---------------------------------------- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set src_modules [glob -nocomplain -dir $current_source_dir -type f -tail $fileglob] |
||||||
|
|
||||||
|
set did_skip 0 ;#flag for stdout/stderr formatting only |
||||||
|
foreach m $src_modules { |
||||||
|
#puts "build_modules_from_source_to_base >>> module $m" |
||||||
|
set fileparts [split [file rootname $m] -] |
||||||
|
set tmfile_versionsegment [lindex $fileparts end] |
||||||
|
if {$tmfile_versionsegment eq $magicversion} { |
||||||
|
#rebuild the .tm from the #tarjar |
||||||
|
set basename [join [lrange $fileparts 0 end-1] -] |
||||||
|
set versionfile $current_source_dir/$basename-buildversion.txt |
||||||
|
set versionfiledata "" |
||||||
|
if {![file exists $versionfile]} { |
||||||
|
puts stderr "\nWARNING: Missing buildversion text file: $versionfile" |
||||||
|
puts stderr "Using version 0.1 - create $versionfile containing the desired version number as the top line to avoid this warning\n" |
||||||
|
set module_build_version "0.1" |
||||||
|
} else { |
||||||
|
set fd [open $versionfile r] |
||||||
|
set versionfiledata [read $fd]; close $fd |
||||||
|
set ln0 [lindex [split $versionfiledata \n] 0] |
||||||
|
set ln0 [string trim $ln0]; set ln0 [string trim $ln0 \r] |
||||||
|
if {![util::is_valid_tm_version $ln0]} { |
||||||
|
puts stderr "ERROR: build version '$ln0' specified in $versionfile is not suitable. Please ensure a proper version number is at first line of file" |
||||||
|
exit 3 |
||||||
|
} |
||||||
|
set module_build_version $ln0 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if {[file exists $current_source_dir/#tarjar-$basename-$magicversion]} { |
||||||
|
file mkdir $buildfolder |
||||||
|
|
||||||
|
if {[file exists $current_source_dir/#tarjar-$basename-$magicversion/DESCRIPTION.txt]} { |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
} |
||||||
|
#REVIEW - should be in same structure/depth as $target_module_dir in _build? |
||||||
|
set tmfile $basedir/_build/$basename-$module_build_version.tm |
||||||
|
file mkdir $basedir/_build |
||||||
|
file delete -force $basedir/_build/#tarjar-$basename-$module_build_version |
||||||
|
file delete -force $tmfile |
||||||
|
|
||||||
|
|
||||||
|
file copy -force $current_source_dir/#tarjar-$basename-$magicversion $basedir/_build/#tarjar-$basename-$module_build_version |
||||||
|
# |
||||||
|
#bsdtar doesn't seem to work.. or I haven't worked out the right options? |
||||||
|
#exec tar -cvf $basedir/_build/$basename-$module_build_version.tm $basedir/_build/#tarjar-$basename-$module_build_version |
||||||
|
package require tar |
||||||
|
tar::create $tmfile $basedir/_build/#tarjar-$basename-$module_build_version |
||||||
|
if {![file exists $tmfile]} { |
||||||
|
puts stdout "ERROR: Failed to build tarjar file $tmfile" |
||||||
|
exit 4 |
||||||
|
} |
||||||
|
#copy the file? |
||||||
|
#set target $target_module_dir/$basename-$module_build_version.tm |
||||||
|
#file copy -force $tmfile $target |
||||||
|
|
||||||
|
lappend module_list $tmfile |
||||||
|
} else { |
||||||
|
#assume that either the .tm is not a tarjar - or the tarjar dir is capped (trailing #) and the .tm has been manually tarred. |
||||||
|
if {[file exists $current_source_dir/#tarjar-$basename-${magicversion}#]} { |
||||||
|
puts stderr "\nWarning: found 'capped' folder #tarjar-$basename-${magicversion}# - No attempt being made to update version in description.txt" |
||||||
|
} |
||||||
|
|
||||||
|
#------------------------------ |
||||||
|
# |
||||||
|
set target_relpath [punkcheck::lib::path_relative $basedir $target_module_dir/$basename-$module_build_version.tm] |
||||||
|
|
||||||
|
set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid] |
||||||
|
|
||||||
|
set changed_list [list] |
||||||
|
# -- --- --- --- --- --- |
||||||
|
set source_relpath [punkcheck::lib::path_relative $basedir $versionfile] |
||||||
|
set file_record [punkcheck::installfile_add_source_and_fetch_metadata $basedir $source_relpath $file_record] |
||||||
|
# -- --- --- --- --- --- |
||||||
|
set source_relpath [punkcheck::lib::path_relative $basedir $current_source_dir/$m] |
||||||
|
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]] |
||||||
|
set changed_list [dict get $changed_unchanged changed] |
||||||
|
|
||||||
|
|
||||||
|
if {[llength $changed_list]} { |
||||||
|
set file_record [punkcheck::installfile_started_install $basedir $file_record] |
||||||
|
# -- --- --- --- --- --- |
||||||
|
set target $target_module_dir/$basename-$module_build_version.tm |
||||||
|
if {$did_skip} {set did_skip 0; puts -nonewline stdout \n} |
||||||
|
puts stdout "copying module $current_source_dir/$m to $target as version: $module_build_version ([file tail $target])" |
||||||
|
set fd [open $current_source_dir/$m r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd |
||||||
|
set data [string map [list $magicversion $module_build_version] $data] |
||||||
|
set fdout [open $target w] |
||||||
|
fconfigure $fdout -translation binary |
||||||
|
puts -nonewline $fdout $data |
||||||
|
close $fdout |
||||||
|
#file copy -force $srcdir/$m $target |
||||||
|
lappend module_list $target |
||||||
|
# -- --- --- --- --- --- |
||||||
|
set file_record [punkcheck::installfile_finished_install $basedir $file_record] |
||||||
|
} else { |
||||||
|
#puts stdout "skipping module $current_source_dir/$m - no change in sources detected" |
||||||
|
puts -nonewline stderr "." |
||||||
|
set did_skip 1 |
||||||
|
set file_record [punkcheck::installfile_skipped_install $basedir $file_record] |
||||||
|
} |
||||||
|
|
||||||
|
#------------------------------ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if {![util::is_valid_tm_version $tmfile_versionsegment]} { |
||||||
|
#last segment doesn't look even slightly versiony - fail. |
||||||
|
puts stderr "ERROR: Unable to confirm file $current_source_dir/$m is a reasonably versioned .tm module - ABORTING." |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
#------------------------------ |
||||||
|
# |
||||||
|
set target_relpath [punkcheck::lib::path_relative $basedir $target_module_dir/$m] |
||||||
|
|
||||||
|
set file_record [punkcheck::installfile_begin $basedir $target_relpath $installername -eventid $punkcheck_eventid] |
||||||
|
|
||||||
|
set changed_list [list] |
||||||
|
# -- --- --- --- --- --- |
||||||
|
set source_relpath [punkcheck::lib::path_relative $basedir $current_source_dir/$m] |
||||||
|
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]] |
||||||
|
set changed_list [dict get $changed_unchanged changed] |
||||||
|
|
||||||
|
if {[llength $changed_list]} { |
||||||
|
set file_record [punkcheck::installfile_started_install $basedir $file_record] |
||||||
|
# -- --- --- --- --- --- |
||||||
|
if {$did_skip} {set did_skip 0; puts -nonewline stdout \n} |
||||||
|
puts stderr "Copied already versioned module $current_source_dir/$m to $target_module_dir" |
||||||
|
lappend module_list $current_source_dir/$m |
||||||
|
file copy -force $current_source_dir/$m $target_module_dir |
||||||
|
# -- --- --- --- --- --- |
||||||
|
set file_record [punkcheck::installfile_finished_install $basedir $file_record] |
||||||
|
} else { |
||||||
|
puts -nonewline stderr "." |
||||||
|
set did_skip 1 |
||||||
|
set file_record [punkcheck::installfile_skipped_install $basedir $file_record] |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
if {$CALLDEPTH >= $max_depth} { |
||||||
|
set subdirs [list] |
||||||
|
} else { |
||||||
|
set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *] |
||||||
|
} |
||||||
|
#puts stderr "subdirs: $subdirs" |
||||||
|
foreach d $subdirs { |
||||||
|
set skipdir 0 |
||||||
|
foreach dg $antidir { |
||||||
|
if {[string match $dg $d]} { |
||||||
|
set skipdir 1 |
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
if {$skipdir} { |
||||||
|
continue |
||||||
|
} |
||||||
|
if {![file exists $target_module_dir/$d]} { |
||||||
|
file mkdir $target_module_dir/$d |
||||||
|
} |
||||||
|
lappend module_list {*}[build_modules_from_source_to_base $srcdir $basedir\ |
||||||
|
-call-depth-internal [expr {$CALLDEPTH +1}]\ |
||||||
|
-subdirlist [list {*}$subdirlist $d]\ |
||||||
|
-punkcheck_eventid $punkcheck_eventid\ |
||||||
|
-glob $fileglob\ |
||||||
|
] |
||||||
|
} |
||||||
|
if {$did_skip} { |
||||||
|
puts -nonewline stdout \n |
||||||
|
} |
||||||
|
return $module_list |
||||||
|
} |
||||||
|
|
||||||
|
proc kettle_call {calltype args} { |
||||||
|
if {$calltype ni [list lib shell]} { |
||||||
|
error "pmix kettle_call 1st argument must be one of: 'lib' for direct use of kettle module or 'shell' to call as separate process" |
||||||
|
} |
||||||
|
if {$calltype eq "shell"} { |
||||||
|
set kettleappfile [file dirname [info nameofexecutable]]/kettle |
||||||
|
set kettlebatfile [file dirname [info nameofexecutable]]/kettle.bat |
||||||
|
|
||||||
|
if {(![file exists $kettleappfile]) && (![file exists $kettlebatfile])} { |
||||||
|
error "pmix kettle_call unable to find installed kettle application file '$kettleappfile' (or '$kettlebatfile' if on windows)" |
||||||
|
} |
||||||
|
if {[file exists $kettleappfile]} { |
||||||
|
set kettlescript $kettleappfile |
||||||
|
} |
||||||
|
if {$::tcl_platform(platform) eq "windows"} { |
||||||
|
if {[file exists $kettlebatfile]} { |
||||||
|
set kettlescript $kettlebatfile |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set startdir [pwd] |
||||||
|
if {![file exists $startdir/build.tcl]} { |
||||||
|
error "pmix kettle must be run from a folder containing build.tcl (cwd: [pwd])" |
||||||
|
} |
||||||
|
if {[catch {package present kettle}]} { |
||||||
|
puts stdout "Loading kettle package - may be delay on first load ..." |
||||||
|
package require kettle |
||||||
|
} |
||||||
|
set first [lindex $args 0] |
||||||
|
if {[string match @* $first]} { |
||||||
|
error "pmix kettle doesn't support special operations - try calling tclsh kettle directly" |
||||||
|
} |
||||||
|
if {$first eq "-f"} { |
||||||
|
set args [lassign $args __ path] |
||||||
|
} else { |
||||||
|
set path $startdir/build.tcl |
||||||
|
} |
||||||
|
set opts [list] |
||||||
|
|
||||||
|
if {[lindex $args 0] eq "-trace"} { |
||||||
|
set args [lrange $args 1 end] |
||||||
|
lappend opts --verbose on |
||||||
|
} |
||||||
|
set goals [list] |
||||||
|
|
||||||
|
if {$calltype eq "lib"} { |
||||||
|
file mkdir ~/.kettle |
||||||
|
set dotfile ~/.kettle/config |
||||||
|
if {[file exists $dotfile] && |
||||||
|
[file isfile $dotfile] && |
||||||
|
[file readable $dotfile]} { |
||||||
|
::kettle io trace {Loading dotfile $dotfile ...} |
||||||
|
set args [list {*}[::kettle path cat $dotfile] {*}$args] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#hardcoded kettle option names (::kettle option names) - retrieved using kettle::option names |
||||||
|
#This is done so we don't have to load kettle lib for shell call (both loading as module and running shell are annoyingly SLOW) |
||||||
|
#REVIEW - needs to be updated to keep in sync with kettle. |
||||||
|
set knownopts [list\ |
||||||
|
--exec-prefix --bin-dir --lib-dir --prefix --man-dir --html-dir --markdown-dir --include-dir \ |
||||||
|
--ignore-glob --dry --verbose --machine --color --state --config --with-shell --log \ |
||||||
|
--log-append --log-mode --with-dia --constraints --file --limitconstraints --tmatch --notfile --single --valgrind --tskip --repeats \ |
||||||
|
--iters --collate --match --rmatch --with-doc-destination --with-git --target --test-include \ |
||||||
|
] |
||||||
|
|
||||||
|
while {[llength $args]} { |
||||||
|
set o [lindex $args 0] |
||||||
|
switch -glob -- $o { |
||||||
|
--* { |
||||||
|
#instead of using: kettle option known |
||||||
|
if {$o ni $knownopts} { |
||||||
|
error "Unable to process unknown option $o." {} [list KETTLE (pmix)] |
||||||
|
} |
||||||
|
lappend opts $o [lindex $args 1] |
||||||
|
#::kettle::option set $o [lindex $args 1] |
||||||
|
set args [lrange $args 2 end] |
||||||
|
} |
||||||
|
default { |
||||||
|
lappend goals $o |
||||||
|
set args [lrange $args 1 end] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {![llength $goals]} { |
||||||
|
lappend goals help |
||||||
|
} |
||||||
|
if {"--prefix" ni [dict keys $opts]} { |
||||||
|
dict set opts --prefix [file dirname $startdir] |
||||||
|
} |
||||||
|
if {$calltype eq "lib"} { |
||||||
|
::kettle status clear |
||||||
|
::kettle::option::set @kettle $startdir |
||||||
|
foreach {o v} $opts { |
||||||
|
::kettle option set $o $v |
||||||
|
} |
||||||
|
::kettle option set @srcscript $path |
||||||
|
::kettle option set @srcdir [file dirname $path] |
||||||
|
::kettle option set @goals $goals |
||||||
|
::source $path |
||||||
|
puts stderr "recipes: [::kettle recipe names]" |
||||||
|
::kettle recipe run {*}[::kettle option get @goals] |
||||||
|
|
||||||
|
set state [::kettle option get --state] |
||||||
|
if {$state ne {}} { |
||||||
|
puts stderr "saving kettle state: $state" |
||||||
|
::kettle status save $state |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
#shell |
||||||
|
puts stdout "Running external kettle process with args: $opts $goals" |
||||||
|
run -n tclsh $kettlescript -f $path {*}$opts {*}$goals |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace eval punk::mix::cli { |
||||||
|
proc _cli {args} { |
||||||
|
#don't use tailcall - base uses info level to determine caller |
||||||
|
::punk::mix::base::_cli {*}$args |
||||||
|
} |
||||||
|
variable default_command help |
||||||
|
package require punk::mix::base |
||||||
|
package require punk::overlay |
||||||
|
punk::overlay::custom_from_base [namespace current] ::punk::mix::base |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::cli [namespace eval punk::mix::cli { |
||||||
|
variable version |
||||||
|
set version 0.3 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,152 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::buildsuite 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::buildsuite { |
||||||
|
namespace export * |
||||||
|
proc projects {suite} { |
||||||
|
set pathinfo [punk::repo::find_repos [pwd]] |
||||||
|
set projectdir [dict get $pathinfo closest] |
||||||
|
set suites_dir [file join $projectdir src buildsuites] |
||||||
|
if {![file isdirectory [file join $suites_dir $suite]]} { |
||||||
|
puts stderr "suite: $suite not found in buildsuites folder: $suites_dir" |
||||||
|
return |
||||||
|
} |
||||||
|
set suite_dir [file join $suites_dir $suite] |
||||||
|
set projects [glob -dir $suite_dir -type d -tails *] |
||||||
|
|
||||||
|
#use internal du which although breadth-first is generally faster |
||||||
|
puts stdout "Examining source folders in $suite_dir." ;#A hint that something is happening in case sources are large |
||||||
|
set du_info [punk::du::du -d 1 -b $suite_dir] |
||||||
|
set du_sizes [dict create] |
||||||
|
set suite_total_size "-" |
||||||
|
foreach du_record $du_info { |
||||||
|
if {[llength $du_record] != 2} { |
||||||
|
#sanity precaution - punk::du::du should always output list of 2 element lists - at least with flags we're using |
||||||
|
continue |
||||||
|
} |
||||||
|
set sz [lindex $du_record 0] |
||||||
|
set path_parts [file split [lindex $du_record 1]] ;#should handle spaced-paths ok. |
||||||
|
set s [lindex $path_parts end-1] |
||||||
|
set p [lindex $path_parts end] |
||||||
|
|
||||||
|
#This handles case where a project folder is same name as suite e.g src/buildsuites/tcl/tcl |
||||||
|
#so we can't just use tail as dict key. We could assume last record is always total - but |
||||||
|
if {![string match -nocase $s $suite]} { |
||||||
|
if {$s eq "buildsuites" && [string match -nocase $p $suite]} { |
||||||
|
set suite_total_size $sz ;#this includes config files in suite base - so we don't really want to use this to report the total source size |
||||||
|
} else { |
||||||
|
#something else - shouldn't happen |
||||||
|
puts stderr "Unexpected output from du in suite_dir: $suite_dir" |
||||||
|
puts stderr "$du_record" |
||||||
|
#try to continue anyway |
||||||
|
} |
||||||
|
} else { |
||||||
|
dict set du_sizes $p $sz |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#build another dict for sizes where we ensure exactly one entry for each project exists and exclude total (don't blindly trust du output e.g in case weird filename/permission issue) |
||||||
|
set psizes [list] |
||||||
|
foreach p $projects { |
||||||
|
if {[dict exists $du_sizes $p]} { |
||||||
|
dict set psizes $p [dict get $du_sizes $p] |
||||||
|
} else { |
||||||
|
dict set psizes $p - |
||||||
|
} |
||||||
|
} |
||||||
|
set total_source_size "-" |
||||||
|
if {[catch { |
||||||
|
set total_source_size [tcl::mathop::+ {*}[dict values $psizes]] |
||||||
|
} errM]} { |
||||||
|
puts stderr "Failed to calculate total source size. Errmsg: $errM" |
||||||
|
} |
||||||
|
package require overtype |
||||||
|
|
||||||
|
set title1 "Projects" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $projects] {punk::strlen $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
|
||||||
|
set size_values [dict values $psizes] |
||||||
|
# Title is probably widest - but go through the process anyway! |
||||||
|
set title2 "Source Bytes" |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $size_values] {punk::strlen $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
|
||||||
|
|
||||||
|
set output "" |
||||||
|
append output "[overtype::left $col1 $title1] [overtype::right $col2 $title2]" \n |
||||||
|
foreach p [lsort $projects] { |
||||||
|
#todo - provide some basic info for each - last build time? last time-to-build? |
||||||
|
append output "[overtype::left $col1 $p] [overtype::right $col2 [dict get $psizes $p]]" \n |
||||||
|
} |
||||||
|
append output "Total Source size: $total_source_size bytes" \n |
||||||
|
return $output |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace eval collection { |
||||||
|
namespace export * |
||||||
|
proc buildsuites {{glob {}}} { |
||||||
|
if {![string length $glob]} { |
||||||
|
set glob * |
||||||
|
} |
||||||
|
#todo - review - we want the furthest not the closest if we are potentially inside a buildsuite project |
||||||
|
set pathinfo [punk::repo::find_repos [pwd]] |
||||||
|
set projectdir [dict get $pathinfo closest] |
||||||
|
set suites_dir [file join $projectdir src buildsuites] |
||||||
|
if {![file exists $suites_dir]} { |
||||||
|
puts stderr "No buildsuites folder found at $suites_dir" |
||||||
|
return |
||||||
|
} |
||||||
|
set suites [lsort [glob -dir $suites_dir -type d -tails *]] |
||||||
|
if {$glob ne "*"} { |
||||||
|
set suites [lsearch -all -inline $suites $glob] |
||||||
|
} |
||||||
|
return $suites |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::buildsuite [namespace eval punk::mix::commandset::buildsuite { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,65 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::debug 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::debug { |
||||||
|
namespace export paths |
||||||
|
namespace path ::punk::mix::cli |
||||||
|
|
||||||
|
proc paths {} { |
||||||
|
set pathinfo [punk::repo::find_repos [pwd]] |
||||||
|
puts stdout "pathinfo: $pathinfo" |
||||||
|
set projectdir [dict get $pathinfo closest] |
||||||
|
puts stdout "closest projectdir: $projectdir" |
||||||
|
set modulefolders [lib::find_source_module_paths $projectdir] |
||||||
|
puts stdout "modulefolders: $modulefolders" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval lib { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::debug [namespace eval punk::mix::commandset::debug { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,140 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::layout 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
#sort of a circular dependency when commandset loaded by punk::mix::cli - that's ok, but this could theoretically be loaded by another cli and with another base |
||||||
|
package require punk::mix |
||||||
|
package require punk::mix::base |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::layout { |
||||||
|
namespace export * |
||||||
|
|
||||||
|
#per layout functions |
||||||
|
proc files {layout} { |
||||||
|
set allfiles [lib::layout_all_files $layout] |
||||||
|
return [join $allfiles \n] |
||||||
|
} |
||||||
|
proc templatefiles {layout} { |
||||||
|
set templatefiles [lib::layout_scan_for_template_files $layout] |
||||||
|
return [join $templatefiles \n] |
||||||
|
} |
||||||
|
proc templatefiles.relative {layout} { |
||||||
|
set tpldir [::punk::mix::base::lib::mix_templates_dir] |
||||||
|
set layout_base $tpldir/layouts |
||||||
|
set layout_dir [file join $layout_base $layout] |
||||||
|
if {![file exists $layout_dir]} { |
||||||
|
puts stderr "Unable to locate folder for layout '$layout' at $layout_dir" |
||||||
|
return |
||||||
|
} |
||||||
|
set stripprefix [file normalize $layout_dir] |
||||||
|
set templatefiles [lib::layout_scan_for_template_files $layout] |
||||||
|
set tails [list] |
||||||
|
foreach templatefullpath $templatefiles { |
||||||
|
lappend tails [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] |
||||||
|
} |
||||||
|
return [join $tails \n] |
||||||
|
} |
||||||
|
|
||||||
|
#layout collection functions - to be imported with punk::overlay::import_commandset separately |
||||||
|
namespace eval collection { |
||||||
|
namespace export * |
||||||
|
proc layouts {{glob {}}} { |
||||||
|
if {![string length $glob]} { |
||||||
|
set glob * |
||||||
|
} |
||||||
|
|
||||||
|
set tpldir [::punk::mix::base::lib::mix_templates_dir] |
||||||
|
set layout_base $tpldir/layouts |
||||||
|
set layouts [glob -nocomplain -dir $layout_base -type d -tail *] |
||||||
|
set layouts [lsort $layouts] |
||||||
|
if {$glob ne "*"} { |
||||||
|
set layouts [lsearch -all -inline $layouts $glob] |
||||||
|
} |
||||||
|
return [join [lsort $layouts] \n] |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
namespace eval lib { |
||||||
|
proc layout_all_files {layout} { |
||||||
|
set tpldir [::punk::mix::base::lib::mix_templates_dir] |
||||||
|
set layoutfolder $tpldir/layouts/$layout |
||||||
|
if {![file isdirectory $layoutfolder]} { |
||||||
|
puts stderr "layout '$layout' not found in $tpldir/layouts" |
||||||
|
} |
||||||
|
set file_list [list] |
||||||
|
util::foreach-file $layoutfolder path { |
||||||
|
lappend file_list $path |
||||||
|
} |
||||||
|
|
||||||
|
return $file_list |
||||||
|
} |
||||||
|
proc layout_scan_for_template_files {layout {tags {}}} { |
||||||
|
#equivalent for projects? punk::mix::commandset::module::lib::templates_dict -scriptpath "" |
||||||
|
set tpldir [::punk::mix::base::lib::mix_templates_dir] |
||||||
|
set layoutfolder $tpldir/layouts/$layout |
||||||
|
if {![file isdirectory $layoutfolder]} { |
||||||
|
puts stderr "layout '$layout' not found in $tpldir/layouts" |
||||||
|
} |
||||||
|
if {![llength $tags]} { |
||||||
|
#todo - get standard tags from somewhere |
||||||
|
set tags [list %project%] |
||||||
|
} |
||||||
|
set file_list [list] |
||||||
|
util::foreach-file $layoutfolder path { |
||||||
|
set fd [open $path r] |
||||||
|
fconfigure $fd -translation binary |
||||||
|
set data [read $fd] |
||||||
|
close $fd |
||||||
|
foreach tag $tags { |
||||||
|
if {[string match "*$tag*" $data]} { |
||||||
|
lappend file_list $path |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $file_list |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::layout [namespace eval punk::mix::commandset::layout { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,529 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::loadedlib 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::loadedlib { |
||||||
|
namespace export * |
||||||
|
#search automatically wrapped in * * - can contain inner * ? globs |
||||||
|
proc search {searchstring} { |
||||||
|
catch {package require frobznodule666} ;#ensure pkg system has loaded/searched for everything |
||||||
|
if {[catch {package require natsort}]} { |
||||||
|
set has_natsort 0 |
||||||
|
} else { |
||||||
|
set has_natsort 1 |
||||||
|
} |
||||||
|
if {[regexp {[?*]} $searchstring]} { |
||||||
|
#caller has specified specific glob pattern - use it |
||||||
|
#todo - respect supplied case only if uppers present? require another flag? |
||||||
|
set matches [lsearch -all -inline -nocase [package names] $searchstring] |
||||||
|
} else { |
||||||
|
#make it easy to search for anything |
||||||
|
set matches [lsearch -all -inline -nocase [package names] "*$searchstring*"] |
||||||
|
} |
||||||
|
|
||||||
|
set matchinfo [list] |
||||||
|
foreach m $matches { |
||||||
|
set versions [package versions $m] |
||||||
|
if {$has_natsort} { |
||||||
|
set versions [natsort::sort $versions] |
||||||
|
} else { |
||||||
|
set versions [lsort $versions] |
||||||
|
} |
||||||
|
lappend matchinfo [list $m $versions] |
||||||
|
} |
||||||
|
return [join [lsort $matchinfo] \n] |
||||||
|
} |
||||||
|
proc loaded.search {searchstring} { |
||||||
|
set search_result [search $searchstring] |
||||||
|
set all_libs [split $search_result \n] |
||||||
|
set col1items [list] |
||||||
|
set col2items [list] |
||||||
|
set col3items [list] |
||||||
|
foreach libinfo $all_libs { |
||||||
|
if {[string trim $libinfo] eq ""} { |
||||||
|
continue |
||||||
|
} |
||||||
|
set versions [lassign $libinfo libname] |
||||||
|
if {[set ver [package provide $libname]] ne ""} { |
||||||
|
lappend col1items $libname |
||||||
|
lappend col2items $versions |
||||||
|
lappend col3items $ver |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
package require overtype |
||||||
|
set title1 "Library" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $col1items] {string length $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
set title2 "Versions Avail." |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $col2items] {string length $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
set title3 "Loaded Version" |
||||||
|
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {string length $v}]] |
||||||
|
set col3 [string repeat " " $widest3] |
||||||
|
|
||||||
|
|
||||||
|
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}] |
||||||
|
|
||||||
|
set table "" |
||||||
|
append table [string repeat - $tablewidth] \n |
||||||
|
append table "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n |
||||||
|
append table [string repeat - $tablewidth] \n |
||||||
|
foreach c1 $col1items c2 $col2items c3 $col3items { |
||||||
|
append table "[overtype::left $col1 $c1] [overtype::left $col2 $c2] [overtype::left $col3 $c3]" \n |
||||||
|
} |
||||||
|
|
||||||
|
return $table |
||||||
|
|
||||||
|
|
||||||
|
set loaded_libs [list] |
||||||
|
foreach libinfo $all_libs { |
||||||
|
if {[string trim $libinfo] eq ""} { |
||||||
|
continue |
||||||
|
} |
||||||
|
set versions [lassign $libinfo libname] |
||||||
|
if {[set ver [package provide $libname]] ne ""} { |
||||||
|
lappend loaded_libs "$libname $versions (loaded $ver)" |
||||||
|
} |
||||||
|
} |
||||||
|
return [join $loaded_libs \n] |
||||||
|
} |
||||||
|
|
||||||
|
proc info {libname} { |
||||||
|
if {[catch {package require natsort}]} { |
||||||
|
set has_natsort 0 |
||||||
|
} else { |
||||||
|
set has_natsort 1 |
||||||
|
} |
||||||
|
catch {package require frobznodule666} ;#ensure pkg system has loaded/searched for everything |
||||||
|
set pkgsknown [package names] |
||||||
|
if {[set posn [lsearch $pkgsknown $libname]] >= 0} { |
||||||
|
puts stdout "Found package [lindex $pkgsknown $posn]" |
||||||
|
} else { |
||||||
|
puts stderr "Package not found as available library/module - check tcl::tm::list and \$auto_path" |
||||||
|
} |
||||||
|
set versions [package versions [lindex $libname 0]] |
||||||
|
if {$has_natsort} { |
||||||
|
set versions [natsort::sort $versions] |
||||||
|
} else { |
||||||
|
set versions [lsort $versions] |
||||||
|
} |
||||||
|
if {![llength $versions]} { |
||||||
|
puts stderr "No version numbers found for library/module $libname" |
||||||
|
return false |
||||||
|
} |
||||||
|
puts stdout "Versions of $libname found: $versions" |
||||||
|
set alphaposn [lsearch $versions "999999.*"] |
||||||
|
if {$alphaposn >= 0} { |
||||||
|
set alpha [lindex $versions $alphaposn] |
||||||
|
#remove and tack onto beginning.. |
||||||
|
set versions [lreplace $versions $alphaposn $alphaposn] |
||||||
|
set versions [list $alpha {*}$versions] |
||||||
|
} |
||||||
|
foreach ver $versions { |
||||||
|
set loadinfo [package ifneeded $libname $ver] |
||||||
|
puts stdout "$libname $ver" |
||||||
|
puts stdout "--- 'package ifneeded' script ---" |
||||||
|
puts stdout $loadinfo |
||||||
|
puts stdout "---" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc copyasmodule {library modulefoldername args} { |
||||||
|
set defaults [list -askme 1] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
set opt_askme [dict get $opts -askme] |
||||||
|
|
||||||
|
if {[catch {package require natsort}]} { |
||||||
|
set has_natsort 0 |
||||||
|
} else { |
||||||
|
set has_natsort 1 |
||||||
|
} |
||||||
|
|
||||||
|
catch {package require frobznodule666} ;#ensure pkg system has loaded/searched for everything |
||||||
|
|
||||||
|
if {[file pathtype $modulefoldername] eq "absolute"} { |
||||||
|
if {![file exists $modulefoldername]} { |
||||||
|
error "Path '$modulefoldername' not found. Enter a fully qualified path, or just the tail such as 'modules' if you are within the project to use <projectdir>/src/modules" |
||||||
|
} |
||||||
|
#use the target folder as the source of projectdir info |
||||||
|
set pathinfo [punk::repo::find_repos $modulefoldername] |
||||||
|
set projectdir [dict get $pathinfo closest] |
||||||
|
set modulefolder_path $modulefoldername |
||||||
|
} else { |
||||||
|
#use the current working directory as the source of projectdir info |
||||||
|
set pathinfo [punk::repo::find_repos [pwd]] |
||||||
|
set projectdir [dict get $pathinfo closest] |
||||||
|
if {$projectdir ne ""} { |
||||||
|
set modulefolders [punk::mix::cli::lib::find_source_module_paths $projectdir] |
||||||
|
foreach k [list modules vendormodules] { |
||||||
|
set knownfolder [file join $projectdir src $k] |
||||||
|
if {$knownfolder ni $modulefolders} { |
||||||
|
lappend modulefolders $knownfolder |
||||||
|
} |
||||||
|
} |
||||||
|
set mtails [list] |
||||||
|
foreach path $modulefolders { |
||||||
|
lappend mtails [file tail $path] |
||||||
|
} |
||||||
|
|
||||||
|
#special case bootsupport/modules so it can be referred to as just bootsupport or bootsupport/modules |
||||||
|
lappend modulefolders [file join $projectdir src bootsupport/modules] |
||||||
|
|
||||||
|
if {$modulefoldername ni $mtails && $modulefoldername ni "bootsupport bootsupport/modules"} { |
||||||
|
set msg "Suplied modulefoldername '$modulefoldername' doesn't appear to be a known module folder within the project at $projectdir\n" |
||||||
|
append msg "Known module folders: [lsort $mtails]\n" |
||||||
|
append msg "Use a name from the above list, or a fully qualified path\n" |
||||||
|
error $msg |
||||||
|
} |
||||||
|
|
||||||
|
if {$modulefoldername eq "bootsupport"} { |
||||||
|
set modulefoldername "bootsupport/modules" |
||||||
|
} |
||||||
|
set modulefolder_path [file join $projectdir src $modulefoldername] |
||||||
|
} else { |
||||||
|
set msg "No current project found at or above current directory\n" |
||||||
|
append msg "Supplied modulefoldername '$modulefoldername' is a name or relative path - cannot use when outside a project." \n |
||||||
|
append msg "Supply an absolute path for the target modulefolder, or try again from within a project directory" \n |
||||||
|
error $msg |
||||||
|
} |
||||||
|
} |
||||||
|
puts stdout "-----------------------------" |
||||||
|
if {$projectdir ne ""} { |
||||||
|
puts stdout "Using projectdir: $projectdir for lib.copyasmodule" |
||||||
|
} else { |
||||||
|
puts stdout "No current project." |
||||||
|
} |
||||||
|
puts stdout "-----------------------------" |
||||||
|
if {![file exists $modulefolder_path]} { |
||||||
|
error "Selected module folder path '$modulefolder_path' doesn't exist. Required subdirectories for namespaced modules will be created automatically - but base selected folder must exist first" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set libfound [lsearch -all -inline [package names] $library] |
||||||
|
if {[llength $libfound] != 1 || ![string length $libfound]} { |
||||||
|
error "Library must match exactly one entry in the list of package names visible to the current interpretor: found '$libfound'" |
||||||
|
} |
||||||
|
|
||||||
|
set versions [package versions [lindex $libfound 0]] |
||||||
|
if {$has_natsort} { |
||||||
|
set versions [natsort::sort $versions] |
||||||
|
} else { |
||||||
|
set versions [lsort $versions] |
||||||
|
} |
||||||
|
if {![llength $versions]} { |
||||||
|
error "No version numbers found for library/module $libfound - sorry, you will need to copy it across manually" |
||||||
|
} |
||||||
|
puts stdout "Versions of $libfound found: $versions" |
||||||
|
set alphaposn [lsearch $versions "999999.*"] |
||||||
|
if {$alphaposn >= 0} { |
||||||
|
set alpha [lindex $versions $alphaposn] |
||||||
|
#remove and tack onto beginning.. |
||||||
|
set versions [lreplace $versions $alphaposn $alphaposn] |
||||||
|
set versions [list $alpha {*}$versions] |
||||||
|
} |
||||||
|
|
||||||
|
set ver [lindex $versions end] ;# todo - make selectable! don't assume tail is latest?.. package vcompare? |
||||||
|
if {[llength $versions] > 1} { |
||||||
|
puts stdout "Version selected: $ver" |
||||||
|
} |
||||||
|
|
||||||
|
set loadinfo [package ifneeded $libfound $ver] |
||||||
|
set loadinfo [string map [list \r\n \n] $loadinfo] |
||||||
|
set loadinfo_lines [split $loadinfo \n] |
||||||
|
if {[catch {llength $loadinfo}]} { |
||||||
|
set loadinfo_is_listshaped 0 |
||||||
|
} else { |
||||||
|
set loadinfo_is_listshaped 1 |
||||||
|
} |
||||||
|
|
||||||
|
#check for redirection to differently cased version of self - this is only detected if this is the only command in the package ifneeded result |
||||||
|
#- must have matching version. REVIEW this requirement. Is there a legitimate reason to divert to a differently cased other-version? |
||||||
|
set is_package_require_self_recased 0 |
||||||
|
set is_package_require_diversion 0 |
||||||
|
set lib_diversion_name "" |
||||||
|
if {[llength $loadinfo_lines] == 1} { |
||||||
|
#e.g Thread 3.0b1 diverts to thread 3.0b1 |
||||||
|
set line1 [lindex $loadinfo_lines 0] |
||||||
|
#check if multiparted with semicolon |
||||||
|
#We need to distinguish "package require <lib> <ver>; more stuff" from "package require <lib> ver> ;" possibly with trailing comment? |
||||||
|
set parts [list] |
||||||
|
if {[regexp {;} $line1]} { |
||||||
|
foreach p [split $line1 {;}] { |
||||||
|
set p [string trim $p] |
||||||
|
if {[string length $p]} { |
||||||
|
#only append parts with some content that doesn't look like a comment |
||||||
|
if {![string match "#*" $p]} { |
||||||
|
lappend parts $p |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
if {[llength $parts] == 1} { |
||||||
|
#seems like a lone package require statement. |
||||||
|
#check if package require, package\trequire etc |
||||||
|
if {[string match "package*require" [lrange $line1 0 1]]} { |
||||||
|
set is_package_require_diversion 1 |
||||||
|
if {[lindex $line1 2] eq "-exact"} { |
||||||
|
#package require -exact <pkg> <ver> |
||||||
|
set lib_diversion_name [lindex $line1 3] |
||||||
|
#check not an exact match - but is a -nocase match - i.e differs in case only |
||||||
|
if {($lib_diversion_name ne $libfound) && [string match -nocase $lib_diversion_name $libfound]} { |
||||||
|
if {[lindex $line1 4] eq $ver} { |
||||||
|
set is_package_require_self_recased 1 |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
#may be package require <pkg> <ver> |
||||||
|
#or package require <pkg> <ver> ?<ver>?... |
||||||
|
set lib_diversion_name [lindex $line1 2] |
||||||
|
#check not an exact match - but is a -nocase match - i.e differs in case only |
||||||
|
if {($lib_diversion_name ne $libfound) && [string match -nocase $lib_diversion_name $libfound]} { |
||||||
|
set requiredversions [lrange $line1 3 end] |
||||||
|
if {$ver in $requiredversions} { |
||||||
|
set is_package_require_self_recased 1 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {$is_package_require_self_recased && [string length $lib_diversion_name]} { |
||||||
|
#we only follow one level of package require redirection - seems unlikely/imprudent to follow arbitrarily in a while loop(?) |
||||||
|
set libfound $lib_diversion_name |
||||||
|
set loadinfo [package ifneeded $libfound $ver] |
||||||
|
set loadinfo [string map [list \r\n \n] $loadinfo] |
||||||
|
set loadinfo_lines [split $loadinfo \n] |
||||||
|
if {[catch {llength $loadinfo}]} { |
||||||
|
set loadinfo_is_listshaped 0 |
||||||
|
} else { |
||||||
|
set loadinfo_is_listshaped 1 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} else { |
||||||
|
if {$is_package_require_diversion} { |
||||||
|
#single |
||||||
|
#for now - we'll abort and tell the user to run again with specified pkg/version |
||||||
|
#We could automate - but it seems likely to be surprising. |
||||||
|
puts stderr "Loadinfo for $libfound seems to be diverting to another pkg/version: $loadinfo_lines" |
||||||
|
puts stderr "Review and consider trying with the pkg/version described in the result above." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if {$loadinfo_is_listshaped && ([llength $loadinfo] == 2 && [lindex $loadinfo 0] eq "source")} { |
||||||
|
set source_file [lindex $loadinfo 1] |
||||||
|
} elseif {[string match "*source*" $loadinfo]} { |
||||||
|
set parts [list] |
||||||
|
foreach ln $loadinfo_lines { |
||||||
|
if {![string length $ln]} {continue} |
||||||
|
lappend parts {*}[split $ln ";"] |
||||||
|
} |
||||||
|
set sources_found [list] |
||||||
|
set loads_found [list] |
||||||
|
set dependencies [list] |
||||||
|
set incomplete_lines [list] |
||||||
|
foreach p $parts { |
||||||
|
set p [string trim $p] |
||||||
|
if {![string length $p]} { |
||||||
|
continue ;#empty line or trailing colon |
||||||
|
} |
||||||
|
if {[string match "*tclPkgSetup*" $p]} { |
||||||
|
puts stderr "Unable to process load script for library $libfound" |
||||||
|
puts stderr "The library appears to use the deprecated tcl library support utility 'tclPkgSetup'" |
||||||
|
return false |
||||||
|
} |
||||||
|
if {![::info complete $p]} { |
||||||
|
# |
||||||
|
#probably a perfectly valid script - but slightly more complicated than we can handle |
||||||
|
#better to defer to manual processing |
||||||
|
lappend incomplete_lines $p |
||||||
|
continue |
||||||
|
} |
||||||
|
if {[lindex $p 0] eq "source"} { |
||||||
|
#may have args.. e.g -encoding utf-8 |
||||||
|
lappend sources_found [lindex $p end] |
||||||
|
} |
||||||
|
if {[lindex $p 0] eq "load"} { |
||||||
|
lappend loads_found [lrange $p 1 end] |
||||||
|
} |
||||||
|
if {[lrange $p 0 1] eq "package require"} { |
||||||
|
lappend dependencies [lrange $p 2 end] |
||||||
|
} |
||||||
|
} |
||||||
|
if {[llength $incomplete_lines]} { |
||||||
|
puts stderr "unable to interpret load script for library $libfound" |
||||||
|
puts stderr "Load info: $loadinfo" |
||||||
|
return false |
||||||
|
} |
||||||
|
if {[llength $loads_found]} { |
||||||
|
puts stderr "package $libfound appears to have binary components" |
||||||
|
foreach l $loads_found { |
||||||
|
puts stderr " binary - $l" |
||||||
|
} |
||||||
|
foreach s $sources_found { |
||||||
|
puts stderr " script - $s" |
||||||
|
} |
||||||
|
puts stderr "Unable to automatically copy binary libraries to your module folder." |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
if {[llength $sources_found] != 1} { |
||||||
|
puts stderr "sorry - unable to interpret source library location" |
||||||
|
puts stderr "Only 1 source supported for now" |
||||||
|
puts stderr "Load info: $loadinfo" |
||||||
|
return false |
||||||
|
} |
||||||
|
if {[llength $dependencies]} { |
||||||
|
#todo - check/ignore if dependency is Tcl ? |
||||||
|
puts stderr "WARNING the package appears to depend on at least one other. Review and copy dependencies if required." |
||||||
|
foreach d $dependencies { |
||||||
|
puts stderr " - $d" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set source_file [lindex $sources_found 0] |
||||||
|
} else { |
||||||
|
puts stderr "sorry - unable to interpret source library location" |
||||||
|
puts stderr "Load info: $loadinfo" |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
# -- --------------------------------------- |
||||||
|
#Analyse source file |
||||||
|
if {![file exists $source_file]} { |
||||||
|
error "Unable to verify source file existence at: $source_file" |
||||||
|
} |
||||||
|
set source_data [fcat $source_file -translation binary] |
||||||
|
if {![string match "*package provide*" $source_data]} { |
||||||
|
puts stderr "Sorry - unable to verify source file contains 'package provide' statement of some sort - copy manually" |
||||||
|
return false |
||||||
|
} else { |
||||||
|
if {![string match "*$libfound*" $source_data]} { |
||||||
|
# as an exception - look for the specific 'package provide $pkg $version' as occurs in the auto-name auto-version modules |
||||||
|
#e.g anyname-0.1.tm example |
||||||
|
if {![string match "*package provide \$pkg \$version*" $source_data]} { |
||||||
|
puts stderr "Sorry - unable to verify source file contains 'package provide' and '$libfound' - copy manually" |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if {[string match "*lappend ::auto_path*" $source_data] || [string match "*lappend auto_path*" $source_data] || [string match "*set ::auto_path*" $source_data]} { |
||||||
|
puts stderr "Sorry - '$libfound' source file '$source_file' appears to rely on ::auto_path and can't be automatically copied as a .tm module" |
||||||
|
puts stderr "Copy the library across to a lib folder instead" |
||||||
|
return false |
||||||
|
} |
||||||
|
# -- --------------------------------------- |
||||||
|
|
||||||
|
set moduleprefix [punk::nsprefix $libfound] |
||||||
|
if {[string length $moduleprefix]} { |
||||||
|
set moduleprefix_parts [punk::nsparts $moduleprefix] |
||||||
|
set relative_path [file join {*}$moduleprefix_parts] |
||||||
|
} else { |
||||||
|
set relative_path "" |
||||||
|
} |
||||||
|
set pkgtail [punk::nstail $libfound] |
||||||
|
set target_path [file join $modulefolder_path $relative_path ${pkgtail}-${ver}.tm] |
||||||
|
|
||||||
|
if {$opt_askme} { |
||||||
|
puts stdout "WARNING - you should check that there aren't extra required files for the library/modules" |
||||||
|
puts stdout "" |
||||||
|
puts stdout "This is not intended for binary modules - use at own risk and check results" |
||||||
|
puts stdout "" |
||||||
|
puts stdout "Base module path: $modulefolder_path" |
||||||
|
puts stdout "Target path : $target_path" |
||||||
|
puts stdout "results of 'package ifneeded $libfound'" |
||||||
|
puts stdout "---" |
||||||
|
puts stdout "$loadinfo" |
||||||
|
puts stdout "---" |
||||||
|
puts stdout "Proceed to create ${pkgtail}-${ver}.tm module? Y|N" |
||||||
|
set stdin_state [fconfigure stdin] |
||||||
|
fconfigure stdin -blocking 1 |
||||||
|
set answer [string tolower [gets stdin]] |
||||||
|
fconfigure stdin -blocking [dict get $stdin_state -blocking] |
||||||
|
if {$answer ne "y"} { |
||||||
|
puts stderr "mix libcopy.asmodule aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {![file exists $modulefolder_path]} { |
||||||
|
puts stdout "Creating module base folder at $modulefolder_path" |
||||||
|
file mkdir $modulefolder_path |
||||||
|
} |
||||||
|
if {![file exists [file dirname $target_path]]} { |
||||||
|
puts stdout "Creating relative folder at [file dirname $target_path]" |
||||||
|
file mkdir [file dirname $target_path] |
||||||
|
} |
||||||
|
|
||||||
|
if {[file exists $target_path]} { |
||||||
|
puts stdout "WARNING - module already exists at $target_path" |
||||||
|
if {$opt_askme} { |
||||||
|
puts stdout "Copy anyway? Y|N" |
||||||
|
set stdin_state [fconfigure stdin] |
||||||
|
fconfigure stdin -blocking 1 |
||||||
|
set answer [string tolower [gets stdin]] |
||||||
|
fconfigure stdin -blocking [dict get $stdin_state -blocking] |
||||||
|
if {$answer ne "y"} { |
||||||
|
puts stderr "mix libcopy.asmodule aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
file copy -force $source_file $target_path |
||||||
|
|
||||||
|
return $target_path |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::loadedlib [namespace eval punk::mix::commandset::loadedlib { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,414 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::module 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::module { |
||||||
|
namespace export * |
||||||
|
|
||||||
|
proc paths {} { |
||||||
|
set roots [punk::repo::find_repos ""] |
||||||
|
set project [lindex [dict get $roots project] 0] |
||||||
|
if {$project ne ""} { |
||||||
|
set is_project 1 |
||||||
|
set searchbase $project |
||||||
|
} else { |
||||||
|
set is_project 0 |
||||||
|
set searchbase [pwd] |
||||||
|
} |
||||||
|
|
||||||
|
if {[catch { |
||||||
|
set source_module_folderlist [punk::mix::cli::lib::find_source_module_paths $searchbase] |
||||||
|
} errMsg]} { |
||||||
|
set source_module_folderlist [list] |
||||||
|
} |
||||||
|
|
||||||
|
set tm_folders [tcl::tm::list] |
||||||
|
package require overtype |
||||||
|
|
||||||
|
set result "" |
||||||
|
if {$is_project} { |
||||||
|
append result "Project module source paths:" \n |
||||||
|
foreach f $source_module_folderlist { |
||||||
|
append result "$f" \n |
||||||
|
} |
||||||
|
} |
||||||
|
append result \n |
||||||
|
append result "tcl::tm::list" \n |
||||||
|
foreach f $tm_folders { |
||||||
|
if {$is_project} { |
||||||
|
if {[punk::mix::cli::lib::path_a_aboveorat_b $project $f]} { |
||||||
|
set pinfo "(within project)" |
||||||
|
} else { |
||||||
|
set pinfo "" |
||||||
|
} |
||||||
|
} else { |
||||||
|
set pinfo "" |
||||||
|
} |
||||||
|
set warning "" |
||||||
|
if {![file isdirectory $f]} { |
||||||
|
set warning "(PATH NOT FOUND)" |
||||||
|
} |
||||||
|
append result "$f $pinfo $warning" \n |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
#require current dir when calling to be the projectdir, or |
||||||
|
proc templates {args} { |
||||||
|
set tdict [templates_dict {*}$args] |
||||||
|
|
||||||
|
package require overtype |
||||||
|
set paths [dict values $tdict] |
||||||
|
set names [dict keys $tdict] |
||||||
|
|
||||||
|
set title1 "Path" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $paths] {punk::strlen $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
|
||||||
|
set title2 "Template Name" |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $names] {punk::strlen $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
|
||||||
|
set tablewidth [expr {$widest1 + 1 + $widest2}] |
||||||
|
set table "" |
||||||
|
append table [string repeat - $tablewidth] \n |
||||||
|
append table "[overtype::left $col1 $title1] [overtype::left $col2 $title2]" \n |
||||||
|
append table [string repeat - $tablewidth] \n |
||||||
|
|
||||||
|
foreach p $paths n $names { |
||||||
|
append table "[overtype::left $col1 $p] [overtype::left $col2 $n]" \n |
||||||
|
} |
||||||
|
|
||||||
|
return $table |
||||||
|
} |
||||||
|
proc templates_dict {args} { |
||||||
|
tailcall lib::templates_dict {*}$args |
||||||
|
} |
||||||
|
proc new {module args} { |
||||||
|
set year [clock format [clock seconds] -format %Y] |
||||||
|
set defaults [list\ |
||||||
|
-project \uFFFF\ |
||||||
|
-version \uFFFF\ |
||||||
|
-license <unspecified>\ |
||||||
|
-template module-0.0.1.tm\ |
||||||
|
-type \uFFFF\ |
||||||
|
-force 0\ |
||||||
|
] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
|
||||||
|
#todo - review compatibility between -template and -type |
||||||
|
#-type is the wrapping technology e.g 'plain' for none or tarjar/zipkit etc (consider also snappy/snappy-tcl) |
||||||
|
#-template may be a folder - but only if the selected -type suports it |
||||||
|
|
||||||
|
|
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
# option -version |
||||||
|
# we need this value before looking at the named argument |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_version_supplied [dict get $opts -version] |
||||||
|
if {$opt_version_supplied eq "\uFFFF"} { |
||||||
|
set opt_version "0.1.0" |
||||||
|
} else { |
||||||
|
set opt_version $opt_version_supplied |
||||||
|
if {![util::is_valid_tm_version $opt_version]} { |
||||||
|
error "pmix module.new error - supplied -version $opt_version doesn't appear to be a valid Tcl module version" |
||||||
|
} |
||||||
|
} |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
#named argument |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set mversion_supplied "" ;#version supplied directly in module argument |
||||||
|
if {[string first - $module]> 0} { |
||||||
|
#if it has a dash then version is required to be valid |
||||||
|
lassign [punk::mix::cli::lib::split_modulename_version $module] modulename mversion |
||||||
|
if {![util::is_valid_tm_version $mversion]} { |
||||||
|
error "pmix module.new error - unable to determine modulename-version from supplied value '$module'" |
||||||
|
} |
||||||
|
set mversion_supplied $mversion ;#record as may need to compare to version from templatefile name |
||||||
|
set vcompare_is_mversion_bigger [package vcompare $mversion $opt_version] |
||||||
|
if {$vcompare_is_mversion_bigger > 0} { |
||||||
|
set opt_version $mversion; #module parameter has higher value than -version |
||||||
|
set vmsg "from module argument: $module" |
||||||
|
} else { |
||||||
|
set vmsg "from -version option: $opt_version_supplied" |
||||||
|
} |
||||||
|
if {$opt_version_supplied ne "\uFFFF"} { |
||||||
|
if {$vcompare_is_mversion_bigger != 0} { |
||||||
|
#is bigger or smaller |
||||||
|
puts stderr "module.new WARNING: version supplied in module argument as well as -version option. Using the higher version number $vmsg" |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
set modulename $module |
||||||
|
} |
||||||
|
punk::mix::cli::lib::validate_modulename $modulename -name_description "mix module.new name" |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
#options |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_project [dict get $opts -project] |
||||||
|
set testdir [pwd] |
||||||
|
if {![string length [set projectdir [punk::repo::find_project $testdir]]]} { |
||||||
|
if {![string length [set projectdir [punk::repo::find_candidate $testdir]]]} { |
||||||
|
error "module.new unable to create module in projectdir:$projectdir - directory doesn't appear to meet basic standards (/src, src/modules, src/lib & /modules must exist, must not be a system path such as /usr/bin or c:/windows)" |
||||||
|
} |
||||||
|
} |
||||||
|
if {$opt_project == "\uFFFF"} { |
||||||
|
set projectname [file tail $projectdir] |
||||||
|
} else { |
||||||
|
set projectname $opt_project |
||||||
|
if {$projectname ne [file tail $projectdir]} { |
||||||
|
error "module.new -project '$opt_project' doesn't match detected projectname '$projectname' at path: $projectdir" |
||||||
|
} |
||||||
|
} |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_license [dict get $opts -license] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_template [dict get $opts -template] |
||||||
|
set lib_tpldir [file join [punk::mix::cli::lib::mix_templates_dir] module];#fallback for modulename_buildversion.txt, modulename_description.txt |
||||||
|
set templates_dict [templates_dict] |
||||||
|
#todo - allow versionless name - pick latest which isn't suffixed with .2 etc |
||||||
|
if {![dict exists $templates_dict $opt_template]} { |
||||||
|
error "module.new unable to find template '$opt_template'. Known templates: [dict keys $templates_dict]" |
||||||
|
} |
||||||
|
set templatefile [dict get $templates_dict $opt_template] |
||||||
|
set tpldir [file dirname $templatefile] ;#use same folder for modulename_buildversion.txt, modulename_description.txt if they exist |
||||||
|
|
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_type [dict get $opts -type] |
||||||
|
if {$opt_type eq "\uFFFF"} { |
||||||
|
set opt_type [lindex [punk::mix::cli::lib::module_types] 0] ;#default to plain |
||||||
|
} |
||||||
|
if {$opt_type ni [punk::mix::cli::lib::module_types]} { |
||||||
|
error "module.new - error - unknown -type '$opt_type' known-types: [punk::mix::cli::lib::module_types]" |
||||||
|
} |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set subpath [punk::mix::cli::lib::module_subpath $modulename] ;#commonly empty string for simple modulename e.g "mymodule" but x::mymodule has subpath 'x' and x::y::mymodule has subpath 'x/y' |
||||||
|
if {![string length $subpath]} { |
||||||
|
set modulefolder $projectdir/src/modules |
||||||
|
} else { |
||||||
|
set modulefolder $projectdir/src/modules/$subpath |
||||||
|
} |
||||||
|
file mkdir $modulefolder |
||||||
|
|
||||||
|
set moduletail [namespace tail $modulename] |
||||||
|
set magicversion [punk::mix::util::magic_tm_version] ;#deliberately large so given load-preference when testing |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set template_tail [file tail $templatefile] ;#convert template_xxx-version.tm.x to {xxx version} |
||||||
|
set template_tail [string range $template_tail [string length template_] end] |
||||||
|
set ext [string tolower [file extension $template_tail]] |
||||||
|
if {$ext eq ".tm"} { |
||||||
|
set template_modulename_part [file rootname $template_tail] |
||||||
|
} elseif {[string is integer -strict [string range $ext 1 end]]} { |
||||||
|
#something like modulename-0.0.1.tm.2 |
||||||
|
#strip of last 2 dotted parts |
||||||
|
set shortened [file rootname $template_tail] |
||||||
|
if {![string equal -nocase [file extension $shortened] ".tm"]} { |
||||||
|
error "module.new error: Unable to interpret filename components of template file '$templatefile' (expected .tm as second-last or last component)" |
||||||
|
} |
||||||
|
set template_modulename_part [file rootname $shortened] |
||||||
|
} else { |
||||||
|
error "module.new error: Unable to interpret filename components of template file '$templatefile'" |
||||||
|
} |
||||||
|
lassign [punk::mix::cli::lib::split_modulename_version $template_modulename_part] t_mname t_version |
||||||
|
#t_version may be empty string if template is unversioned e.g template_whatever.tm |
||||||
|
|
||||||
|
set fd [open $templatefile r]; set template_filedata [read $fd]; close $fd |
||||||
|
if {[string match "*$magicversion*" $template_filedata]} { |
||||||
|
set use_magic 1 |
||||||
|
set build_version $opt_version |
||||||
|
set infile_version $magicversion |
||||||
|
} else { |
||||||
|
set use_magic 0 |
||||||
|
if {$opt_version_supplied ne "\uFFFF"} { |
||||||
|
set build_version $opt_version |
||||||
|
} else { |
||||||
|
if {[util::is_valid_tm_version $t_version]} { |
||||||
|
if {$mversion_supplied eq ""} { |
||||||
|
set build_version $t_version |
||||||
|
} else { |
||||||
|
#we have a version from the named argument 'module' |
||||||
|
if {[package vcompare $mversion_supplied $t_version] > 0} { |
||||||
|
set build_version $mversion_supplied |
||||||
|
} else { |
||||||
|
set build_version $t_version |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
#probably an unversioned module template |
||||||
|
#use opt_version default from above |
||||||
|
set build_version $opt_version |
||||||
|
} |
||||||
|
} |
||||||
|
set infile_version $build_version |
||||||
|
} |
||||||
|
|
||||||
|
set template_filedata [string map [list %pkg% $modulename %year% $year %license% $opt_license %version% $infile_version] $template_filedata] |
||||||
|
|
||||||
|
set modulefile $modulefolder/${moduletail}-$infile_version.tm |
||||||
|
if {[file exists $modulefile]} { |
||||||
|
set errmsg "module.new error: module file $modulefile already exists - aborting" |
||||||
|
if {[string match "*$magicversion*" $modulefile]} { |
||||||
|
append errmsg \n "If you are attempting to create a module file with a specific version in the source-file name - you will need to use a template that doesn't contain the string '$magicversion' e.g the provided template moduleexactversion-0.0.1.tm" |
||||||
|
} |
||||||
|
error $errmsg |
||||||
|
} |
||||||
|
|
||||||
|
if {[file exists $tpldir/modulename_buildversion.txt]} { |
||||||
|
set fd [open $tpldir/modulename_buildversion.txt r]; set buildversion_filedata [read $fd]; close $fd |
||||||
|
} else { |
||||||
|
set fd [open $lib_tpldir/modulename_buildversion.txt r]; set buildversion_filedata [read $fd]; close $fd |
||||||
|
} |
||||||
|
set buildversionfile [file join $modulefolder ${moduletail}-buildversion.txt] |
||||||
|
set existing_build_version "" |
||||||
|
if {[file exists $buildversionfile]} { |
||||||
|
set buildversiondata [punk::mix::util::fcat $buildversionfile] |
||||||
|
set lines [split $buildversiondata \n] |
||||||
|
set existing_build_version [string trim [lindex $lines 0]] |
||||||
|
if {[package vcompare $existing_build_version $build_version] >= 0} { |
||||||
|
#existing version in -buildversion.txt file is lower than the module version we are creating |
||||||
|
error "module.new error: there is an existing buildversion file $buildversionfile with version $existing_build_version equal to or higher than $build_version - unable to continue" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set existing_versions [glob -nocomplain -dir $modulefolder -tails ${moduletail}-*.tm] |
||||||
|
#it shouldn't be possible to overmatch with the glob - because '-' is not valid in a Tcl module name |
||||||
|
if {[llength $existing_versions]} { |
||||||
|
set name_version_pairs [list] |
||||||
|
lappend name_version_pairs [list $moduletail $infile_version] |
||||||
|
foreach existing $existing_versions { |
||||||
|
lappend name_version_pairs [punk::mix::cli::lib::split_modulename_version $existing] ;# .tm is stripped and ignored |
||||||
|
} |
||||||
|
set name_version_pairs [lsort -command {package vcompare} -index 1 $name_version_pairs] ;#while plain lsort will often work with versions - it can get order wrong with some - so use package vcompare |
||||||
|
if {[lindex $name_version_pairs end] ne [list $moduletail $infile_version]} { |
||||||
|
set thisposn [lsearch -index 1 $name_version_pairs $infile_version] |
||||||
|
set name_version_pairs [lreplace $name_version_pairs $thisposn $thisposn] |
||||||
|
set other_versions [lsearch -all -inline -index 1 -subindices $name_version_pairs *] |
||||||
|
set errmsg "module.new error: There are existing modules in the target folder with higher versions than $infile_version." |
||||||
|
append errmsg \n "Other versions found: $other_versions" |
||||||
|
if {$magicversion in $other_versions} { |
||||||
|
append errmsg \n "Existing build version for special source file name: '$magicversion' is: '$existing_build_version'" |
||||||
|
append errmsg \n "If '$magicversion' file doesn't represent the latest source it should be removed or the filename and contents adjusted to be a specific version" |
||||||
|
} |
||||||
|
error $errmsg |
||||||
|
} else { |
||||||
|
puts stderr "module.new WARNING: There are existing modules in the target folder with lower versions than $infile_version - manual review recommended" |
||||||
|
puts stderr "Other versions found: [lsearch -all -inline -index 1 -subindices [lrange $name_version_pairs 0 end-1] *]" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set fd [open $modulefile w] |
||||||
|
fconfigure $fd -translation binary |
||||||
|
puts -nonewline $fd $template_filedata |
||||||
|
close $fd |
||||||
|
|
||||||
|
|
||||||
|
set buildversion_filedata [string map [list %Major.Minor.Level% $build_version] $buildversion_filedata] |
||||||
|
set fd [open $buildversionfile w] |
||||||
|
fconfigure $fd -translation binary |
||||||
|
puts -nonewline $fd $buildversion_filedata |
||||||
|
close $fd |
||||||
|
|
||||||
|
return [list file $modulefile version $build_version] |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval lib { |
||||||
|
proc templates_dict {args} { |
||||||
|
set defaults [list -scriptpath ""] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
set opt_scriptpath [dict get $opts -scriptpath] |
||||||
|
|
||||||
|
set module_tfolders [list] |
||||||
|
set tfolders [punk::mix::base::lib::get_template_folders $opt_scriptpath] |
||||||
|
foreach tf $tfolders { |
||||||
|
lappend module_tfolders [file join $tf module] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set template_files [list] |
||||||
|
foreach fld $module_tfolders { |
||||||
|
set matched_files [glob -nocomplain -dir $fld -type f template_*] |
||||||
|
foreach tf $matched_files { |
||||||
|
if {[string match ignore* $tf]} { |
||||||
|
continue |
||||||
|
} |
||||||
|
set ext [file extension $tf] |
||||||
|
if {$ext in [list ".tm"]} { |
||||||
|
lappend template_files $tf |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set tdict [dict create] |
||||||
|
set seen_dict [dict create] |
||||||
|
foreach fullpath $template_files { |
||||||
|
set ftail [file tail $fullpath] |
||||||
|
set tname [string range $ftail [string length template_] end] |
||||||
|
if {![dict exists $seen_dict $tname]} { |
||||||
|
dict set seen_dict $tname 1 |
||||||
|
dict set tdict $tname $fullpath ; #first seen of filename gets no number |
||||||
|
} else { |
||||||
|
set n [dict get $seen_dict $tname] |
||||||
|
incr n |
||||||
|
dict incr seen_dict $tname |
||||||
|
dict set tdict ${tname}.$n $fullpath |
||||||
|
} |
||||||
|
} |
||||||
|
return $tdict |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::module [namespace eval punk::mix::commandset::module { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,734 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::project 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::project { |
||||||
|
namespace export * |
||||||
|
|
||||||
|
#new project structure - may be dedicated to one module, or contain many. |
||||||
|
#create minimal folder structure only by specifying -modules {} |
||||||
|
proc new {newprojectpath_or_name args} { |
||||||
|
if {[file pathtype $newprojectpath_or_name] eq "absolute"} { |
||||||
|
set projectfullpath [file normalize $newprojectpath_or_name] |
||||||
|
set projectname [file tail $projectfullpath] |
||||||
|
set projectparentdir [file dirname $newprojectpath_or_name] |
||||||
|
} else { |
||||||
|
set projectfullpath [file join [pwd] $newprojectpath_or_name] |
||||||
|
set projectname [file tail $projectfullpath] |
||||||
|
set projectparentdir [file dirname $projectfullpath] |
||||||
|
} |
||||||
|
if {[file type $projectparentdir] ne "directory"} { |
||||||
|
error "punk::mix::cli::new error: unable to determine containing folder for '$newprojectpath_or_name'" |
||||||
|
} |
||||||
|
|
||||||
|
punk::mix::cli::lib::validate_projectname $projectname -name_description "punk mix project.new" |
||||||
|
|
||||||
|
|
||||||
|
set defaults [list\ |
||||||
|
-type plain\ |
||||||
|
-empty 0\ |
||||||
|
-force 0\ |
||||||
|
-update 0\ |
||||||
|
-confirm 1\ |
||||||
|
-modules \uFFFF\ |
||||||
|
-layout project |
||||||
|
] ;#todo |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_type [dict get $opts -type] |
||||||
|
if {$opt_type ni [punk::mix::cli::lib::module_types]} { |
||||||
|
error "pmix new error - unknown type '$opt_type' known types: [punk::mix::cli::lib::module_types]" |
||||||
|
} |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_force [dict get $opts -force] |
||||||
|
set opt_confirm [string tolower [dict get $opts -confirm]] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_modules [dict get $opts -modules] |
||||||
|
if {[llength $opt_modules] == 1 && [lindex $opt_modules 0] eq "\uFFFF"} { |
||||||
|
#if not specified - add a single module matching project name |
||||||
|
set opt_modules [list $projectname] |
||||||
|
} |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
set opt_layout [dict get $opts -layout] |
||||||
|
set opt_update [dict get $opts -update] |
||||||
|
# -- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
|
||||||
|
|
||||||
|
set fossil_prog [auto_execok fossil] |
||||||
|
if {![string length $fossil_prog]} { |
||||||
|
puts stderr "The fossil program was not found. A fossil executable is required to use most pmix features." |
||||||
|
if {[string length [set scoop_prog [auto_execok scoop]]]} { |
||||||
|
#restrict to windows? |
||||||
|
set answer [util::askuser "scoop detected. Would you like pmix to install fossil now using scoop? Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
#we don't assume 'unknown' is configured to run shell commands |
||||||
|
if {[string length [package provide shellrun]]} { |
||||||
|
set exitinfo [run {*}$scoop_prog install fossil] |
||||||
|
#scoop tends to return successful exitcode (0) even when packages not found etc. - so exitinfo not much use. |
||||||
|
puts stdout "scoop install fossil ran with result: $exitinfo" |
||||||
|
} else { |
||||||
|
puts stdout "Please wait while scoop runs - there may be a slight delay and then scoop output will be shown. (use punk shellrun package for )" |
||||||
|
set result [exec {*}$scoop_prog install fossil] |
||||||
|
puts stdout $result |
||||||
|
} |
||||||
|
catch {::auto_reset} ;#can be missing (unsure under what circumstances - but I've seen it raise error 'invalid command name "auto_reset"') |
||||||
|
if {![string length [auto_execok fossil]]} { |
||||||
|
puts stderr "Fossil still not detected. If it was successfully installed, try restarting your punk/tcl shell." |
||||||
|
return |
||||||
|
} |
||||||
|
#todo - ask user if they want to configure fosssil first.. |
||||||
|
set answer [util::askuser "Fossil command now appears to be available. You may wish to answer N to exit and customize it - but default config may be ok. Type the word 'continue' to proceed with default configuration."] |
||||||
|
if {[string tolower $answer] ne "continue"} { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
puts stdout "See: https://fossil-scm.org/home/uv/download.html" |
||||||
|
if {"windows" eq $::tcl_platform(platform)} { |
||||||
|
puts stdout "Consider using a package manager such as scoop: https://scoop.sh" |
||||||
|
puts stdout "(Then: scoop install fossil)" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set startdir [pwd] |
||||||
|
if {[set in_project [punk::repo::find_project $startdir]] ne ""} { |
||||||
|
# use this project as source of templates |
||||||
|
puts stdout "-------------------------------------------" |
||||||
|
puts stdout "Currently in a project directory '$in_project'" |
||||||
|
puts stdout "This project will be searched for templates" |
||||||
|
puts stdout "-------------------------------------------" |
||||||
|
} |
||||||
|
|
||||||
|
#todo - detect whether inside cwd-project or inside a different project |
||||||
|
set projectdir $projectparentdir/$projectname |
||||||
|
if {[set target_in_project [punk::repo::find_project $projectparentdir]] ne ""} { |
||||||
|
puts stderr "Target location for new project is already within a project: $target_in_project" |
||||||
|
error "Nested projects not yet supported aborting" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set repodb_folder [punk::repo::fossil_get_repository_folder_for_project $projectname -parentfolder $startdir] |
||||||
|
if {![string length $repodb_folder]} { |
||||||
|
puts stderr "No usable repository database folder selected for $projectname.fossil file" |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if {[file exists $repodb_folder/$projectname.fossil]} { |
||||||
|
puts stdout "NOTICE: $repodb_folder/$projectname.fossil already exists" |
||||||
|
if {!($opt_force || $opt_update)} { |
||||||
|
puts stderr "-force 1 or -update 1 not specified - aborting" |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#punk::mix::base::lib::get_template_folders |
||||||
|
#punk::mix::commandset::module::lib::templates_dict -scriptpath "" |
||||||
|
|
||||||
|
|
||||||
|
set tpldir [punk::mix::cli::lib::mix_templates_dir] |
||||||
|
|
||||||
|
if {[file exists $projectdir] && !($opt_force || $opt_update)} { |
||||||
|
puts stderr "Unable to create new project at $projectdir - file/folder already exists use -update 1 to fill in missing items from template use -force 1 to overwrite from template" |
||||||
|
return |
||||||
|
} elseif {[file exists $projectdir] && $opt_force} { |
||||||
|
puts stderr "mix new WARNING: -force 1 was supplied. Will copy layout $tpldir/layouts/$opt_layout using -force option to overwrite from template" |
||||||
|
if {$opt_confirm ni [list 0 no false]} { |
||||||
|
set answer [util::askuser "Do you want to proceed to possibly overwrite existing files in $projectdir? Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} elseif {[file exists $projectdir] && $opt_update} { |
||||||
|
puts stderr "mix new WARNING: -update 1 was supplied. Will copy layout $tpldir/layouts/$opt_layout using -update option to add missing items" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if {[punk::repo::is_git $projectparentdir]} { |
||||||
|
puts stderr "mix new WARNING: target project location is within a git repo based at [punk::repo::find_git $projectparentdir]" |
||||||
|
puts stderr "The new project will create a fossil repository (which you are free to ignore - but but will be used to confirm project base)" |
||||||
|
puts stderr "If you intend to use both git and fossil in the same project space - you should research and understand the details and any possible interactions/issues" |
||||||
|
set answer [util::askuser "Do you want to proceed to create a project based at: $projectdir? Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
set is_nested_fossil 0 ;#default assumption |
||||||
|
if {[punk::repo::is_fossil $projectparentdir]} { |
||||||
|
puts stderr "mix new WARNING: target project location is within an open fossil repo based at [punk::repo::find_fossil $projectparentdir] NESTED fossil repository" |
||||||
|
if {$opt_confirm ni [list 0 no false]} { |
||||||
|
puts stderr "If you proceed - the new project's fossil repo will be created using the --nested flag" |
||||||
|
set answer [util::askuser "Do you want to proceed to create a NESTED project based at: $projectdir? Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
set is_nested_fossil 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
puts stdout "Initialising fossil repo: $repodb_folder/$projectname.fossil" |
||||||
|
set fossilinit [runx -n {*}$fossil_prog init $repodb_folder/$projectname.fossil -project-name $projectname] |
||||||
|
if {[dict get $fossilinit exitcode] != 0} { |
||||||
|
puts stderr "fossil init failed:" |
||||||
|
puts stderr [dict get $fossilinit stderr] |
||||||
|
return |
||||||
|
} else { |
||||||
|
puts stdout "fossil init result:" |
||||||
|
puts stdout [dict get $fossilinit stdout] |
||||||
|
} |
||||||
|
|
||||||
|
file mkdir $projectdir |
||||||
|
set layout_dir $tpldir/layouts/$opt_layout |
||||||
|
puts stdout ">>> about to call punkcheck::install $layout_dir $projectdir" |
||||||
|
#In this case we need to override the default dir antiglob - as .fossil- folders need to be installed from template |
||||||
|
## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"] |
||||||
|
set override_antiglob_dir_core [list #* _aside .git] |
||||||
|
if {$opt_force} { |
||||||
|
punkcheck::install $layout_dir $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite ALL-TARGETS |
||||||
|
#file copy -force $layout_dir $projectdir |
||||||
|
} else { |
||||||
|
punkcheck::install $layout_dir $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core |
||||||
|
} |
||||||
|
|
||||||
|
#lappend substfiles $projectdir/README.md |
||||||
|
#lappend substfiles $projectdir/src/README.md |
||||||
|
#lappend substfiles $projectdir/src/doc/main.man |
||||||
|
#expect this in all templates? - todo make these substitutions independent of specific paths and filenames? |
||||||
|
#scan all files in template |
||||||
|
# |
||||||
|
#TODO - pmix command to substitute templates? |
||||||
|
set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout] |
||||||
|
set stripprefix [file normalize $layout_dir] |
||||||
|
|
||||||
|
foreach templatefullpath $templatefiles { |
||||||
|
set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix] |
||||||
|
|
||||||
|
set fpath [file join $projectdir $templatetail] |
||||||
|
if {[file exists $fpath]} { |
||||||
|
set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd |
||||||
|
set data [string map [list %project% $projectname] $data] |
||||||
|
set fdout [open $fpath w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data; close $fdout |
||||||
|
} else { |
||||||
|
puts stderr "warning: Missing template file $fpath" |
||||||
|
} |
||||||
|
} |
||||||
|
#todo - tag substitutions in src/doc tree |
||||||
|
|
||||||
|
|
||||||
|
cd $projectdir |
||||||
|
|
||||||
|
foreach m $opt_modules { |
||||||
|
punk::mix::commandset::module::new $m -project $projectname -type $opt_type -force $opt_force |
||||||
|
} |
||||||
|
|
||||||
|
#generate www/man/md output in 'embedded' folder which should be checked into repo for online documentation |
||||||
|
cd $projectdir/src |
||||||
|
punk::mix::cli::lib::kettle_call lib doc |
||||||
|
#Kettle doc |
||||||
|
|
||||||
|
cd $projectdir |
||||||
|
|
||||||
|
if {![punk::repo::is_fossil_root $projectdir]} { |
||||||
|
set first_fossil 1 |
||||||
|
#-k = keep. (only modify the manifest file(s)) |
||||||
|
if {$is_nested_fossil} { |
||||||
|
set fossilopen [runx -n {*}$fossil_prog open --nested $repodb_folder/$projectname.fossil -k --workdir $projectdir] |
||||||
|
} else { |
||||||
|
set fossilopen [runx -n {*}$fossil_prog open $repodb_folder/$projectname.fossil -k --workdir $projectdir] |
||||||
|
} |
||||||
|
if {[file exists $projectdir/_FOSSIL_] && ![file exists $projectdir/.fslckout]} { |
||||||
|
file rename $projectdir/_FOSSIL_ $projectdir/.fslckout |
||||||
|
} |
||||||
|
if {[dict get $fossilopen exitcode] != 0} { |
||||||
|
puts stderr "fossil open in project workdir '$projectdir' FAILED:" |
||||||
|
puts stderr [dict get $fossilopen stderr] |
||||||
|
return |
||||||
|
} else { |
||||||
|
puts stdout "fossil open in project workdir '$projectdir' OK:" |
||||||
|
puts stdout [dict get $fossilopen stdout] |
||||||
|
} |
||||||
|
} else { |
||||||
|
set first_fossil 0 |
||||||
|
} |
||||||
|
set fossiladd [runx -n {*}$fossil_prog add --dotfiles $projectdir] |
||||||
|
if {[dict get $fossiladd exitcode] != 0} { |
||||||
|
puts stderr "fossil add workfiles in workdir '$projectdir' FAILED:" |
||||||
|
puts stderr [dict get $fossiladd stderr] |
||||||
|
return |
||||||
|
} else { |
||||||
|
puts stdout "fossil add workfiles in workdir '$projectdir' OK:" |
||||||
|
puts stdout [dict get $fossiladd stdout] |
||||||
|
} |
||||||
|
if {$first_fossil} { |
||||||
|
#fossil commit may prompt user for input.. runx runout etc will pause with no prompts |
||||||
|
util::do_in_path $projectdir { |
||||||
|
set fossilcommit [run -n {*}$fossil_prog commit -m "initial project commit"] |
||||||
|
} |
||||||
|
if {[dict get $fossilcommit exitcode] != 0} { |
||||||
|
puts stderr "fossil commit in workdir '$projectdir' FAILED" |
||||||
|
return |
||||||
|
} else { |
||||||
|
puts stdout "fossil commit in workdir '$projectdir' OK" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
puts stdout "-done- project:$projectname projectdir: $projectdir" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace eval collection { |
||||||
|
namespace export * |
||||||
|
namespace path [namespace parent] |
||||||
|
|
||||||
|
proc projects {{glob {}} args} { |
||||||
|
package require overtype |
||||||
|
set db_projects [lib::get_projects $glob] |
||||||
|
set col1items [lsearch -all -inline -index 0 -subindices $db_projects *] |
||||||
|
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] |
||||||
|
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] |
||||||
|
set col3items [lmap v $checkouts {llength $v}] |
||||||
|
|
||||||
|
set title1 "Fossil DB" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $col1items] {punk::strlen $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
set title2 "File Name" |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $col2items] {punk::strlen $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
set title3 "Checkouts" |
||||||
|
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {punk::strlen $v}]] |
||||||
|
set col3 [string repeat " " $widest3] |
||||||
|
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3}] |
||||||
|
|
||||||
|
|
||||||
|
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]" \n |
||||||
|
append msg [string repeat "=" $tablewidth] \n |
||||||
|
foreach p $col1items n $col2items c $col3items { |
||||||
|
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]" \n |
||||||
|
} |
||||||
|
return $msg |
||||||
|
#return [list_as_lines [lib::get_projects $glob]] |
||||||
|
} |
||||||
|
proc projects.detail {{glob {}} args} { |
||||||
|
package require overtype |
||||||
|
package require textutil |
||||||
|
set defaults [dict create\ |
||||||
|
-description 0\ |
||||||
|
] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- |
||||||
|
set opt_description [dict get $opts -description] |
||||||
|
# -- --- --- --- --- --- --- |
||||||
|
|
||||||
|
|
||||||
|
set db_projects [lib::get_projects $glob] |
||||||
|
set col1_dbfiles [lsearch -all -inline -index 0 -subindices $db_projects *] |
||||||
|
set col2items [lsearch -all -inline -index 1 -subindices $db_projects *] |
||||||
|
set checkouts [lsearch -all -inline -index 2 -subindices $db_projects *] |
||||||
|
set col3items [lmap v $checkouts {llength $v}] |
||||||
|
|
||||||
|
set col4_pnames [list] |
||||||
|
set col5_pcodes [list] |
||||||
|
set col6_dupids [list] |
||||||
|
set col7_pdescs [list] |
||||||
|
set codes [dict create] |
||||||
|
foreach dbfile $col1_dbfiles { |
||||||
|
set project_name "" |
||||||
|
set project_code "" |
||||||
|
set project_desc "" |
||||||
|
sqlite3 dbp $dbfile |
||||||
|
dbp eval {select name,value from config where name like 'project-%';} r { |
||||||
|
if {$r(name) eq "project-name"} { |
||||||
|
set project_name $r(value) |
||||||
|
} elseif {$r(name) eq "project-code"} { |
||||||
|
set project_code $r(value) |
||||||
|
} elseif {$r(name) eq "project-description"} { |
||||||
|
set project_desc $r(value) |
||||||
|
} |
||||||
|
} |
||||||
|
dbp close |
||||||
|
lappend col4_pnames $project_name |
||||||
|
lappend col5_pcodes $project_code |
||||||
|
dict lappend codes $project_code $dbfile |
||||||
|
lappend col7_pdescs $project_desc |
||||||
|
} |
||||||
|
|
||||||
|
set setid 1 |
||||||
|
set codeset [dict create] |
||||||
|
dict for {code dbs} $codes { |
||||||
|
if {[llength $dbs]>1} { |
||||||
|
dict set codeset $code setid $setid |
||||||
|
dict set codeset $code count [llength $dbs] |
||||||
|
dict set codeset $code seen 0 |
||||||
|
incr setid |
||||||
|
} |
||||||
|
} |
||||||
|
set dupid 1 |
||||||
|
foreach pc $col5_pcodes { |
||||||
|
if {[dict exists $codeset $pc]} { |
||||||
|
set seen [dict get $codeset $pc seen] |
||||||
|
set this_seen [expr {$seen + 1}] |
||||||
|
dict set codeset $pc seen $this_seen |
||||||
|
lappend col6_dupids "[dict get $codeset $pc setid].${this_seen}/[dict get $codeset $pc count]" |
||||||
|
} else { |
||||||
|
lappend col6_dupids "" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set title1 "Fossil DB" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $col1_dbfiles] {punk::strlen $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
set title2 "File Name" |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $col2items] {punk::strlen $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
set title3 "Checkouts" |
||||||
|
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col3items] {punk::strlen $v}]] |
||||||
|
set col3 [string repeat " " $widest3] |
||||||
|
set title4 "Project Name" |
||||||
|
set widest4 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col4_pnames] {punk::strlen $v}]] |
||||||
|
set col4 [string repeat " " $widest4] |
||||||
|
set title5 "Project Code" |
||||||
|
set widest5 [tcl::mathfunc::max {*}[lmap v [concat [list $title5] $col5_pcodes] {punk::strlen $v}]] |
||||||
|
set col5 [string repeat " " $widest5] |
||||||
|
set title6 "Dup" |
||||||
|
set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col6_dupids] {punk::strlen $v}]] |
||||||
|
set col6 [string repeat " " $widest6] |
||||||
|
set title7 "Description" |
||||||
|
#set widest7 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col7_pdescs] {punk::strlen $v}]] |
||||||
|
set widest7 35 |
||||||
|
set col7 [string repeat " " $widest7] |
||||||
|
|
||||||
|
|
||||||
|
set tablewidth [expr {$widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5 + 1 + $widest6}] |
||||||
|
|
||||||
|
append msg "[overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3]\ |
||||||
|
[overtype::left $col4 $title4] [overtype::left $col5 $title5] [overtype::left $col6 $title6]" |
||||||
|
if {!$opt_description} { |
||||||
|
append msg \n |
||||||
|
} else { |
||||||
|
append msg "[overtype::left $col7 $title7]" \n |
||||||
|
set tablewidth [expr {$tablewidth + 1 + $widest7}] |
||||||
|
} |
||||||
|
|
||||||
|
append msg [string repeat "=" $tablewidth] \n |
||||||
|
foreach p $col1_dbfiles n $col2items c $col3items pn $col4_pnames pc $col5_pcodes dup $col6_dupids desc $col7_pdescs { |
||||||
|
set desclines [split [textutil::adjust $desc -length $widest7] \n] |
||||||
|
set desc1 [lindex $desclines 0] |
||||||
|
append msg "[overtype::left $col1 $p] [overtype::left $col2 $n] [overtype::right $col3 $c]\ |
||||||
|
[overtype::left $col4 $pn] [overtype::left $col5 $pc] [overtype::left $col6 $dup]" |
||||||
|
if {!$opt_description} { |
||||||
|
append msg \n |
||||||
|
} else { |
||||||
|
append msg " [overtype::left $col7 $desc1]" \n |
||||||
|
foreach dline [lrange $desclines 1 end] { |
||||||
|
append msg "$col1 $col2 $col3 $col4 $col5 $col6 [overtype::left $col7 $dline]" \n |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $msg |
||||||
|
#return [list_as_lines [lib::get_projects $glob]] |
||||||
|
} |
||||||
|
proc projects.cd {{glob {}} args} { |
||||||
|
dict set args -cd 1 |
||||||
|
projects.work $glob {*}$args |
||||||
|
} |
||||||
|
proc projects.work {{glob {}} args} { |
||||||
|
package require sqlite3 |
||||||
|
set db_projects [lib::get_projects $glob] |
||||||
|
#list of lists of the form: |
||||||
|
#{fosdb fname workdirlist} |
||||||
|
set defaults [dict create\ |
||||||
|
-cd 0\ |
||||||
|
] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
# -- --- --- --- --- --- --- |
||||||
|
set opt_cd [dict get $opts -cd] |
||||||
|
# -- --- --- --- --- --- --- |
||||||
|
set workdir_dict [dict create] |
||||||
|
set all_workdirs [list] |
||||||
|
foreach pinfo $db_projects { |
||||||
|
lassign $pinfo fosdb name workdirs |
||||||
|
foreach wdir $workdirs { |
||||||
|
dict set workdir_dict $wdir $pinfo |
||||||
|
lappend all_workdirs $wdir |
||||||
|
} |
||||||
|
} |
||||||
|
set col_rowids [list] |
||||||
|
set workdirs [lsort -index 0 $all_workdirs] |
||||||
|
set col_dupids [list] |
||||||
|
set col_fnames [list] |
||||||
|
set col_pnames [list] |
||||||
|
set col_pcodes [list] |
||||||
|
set col_dupids [list] |
||||||
|
|
||||||
|
set fosdb_count [dict create] |
||||||
|
set fosdb_dupset [dict create] |
||||||
|
set fosdb_cache [dict create] |
||||||
|
set dupset 0 |
||||||
|
set rowid 1 |
||||||
|
foreach wd $workdirs { |
||||||
|
set wdinfo [dict get $workdir_dict $wd] |
||||||
|
lassign $wdinfo fosdb nm siblingworkdirs |
||||||
|
dict incr fosdb_count $fosdb |
||||||
|
set dbcount [dict get $fosdb_count $fosdb] |
||||||
|
if {[llength $siblingworkdirs] > 1} { |
||||||
|
if {![dict exists $fosdb_dupset $fosdb]} { |
||||||
|
#first time this multi-checkout fosdb seen |
||||||
|
dict set fosdb_dupset $fosdb [incr dupset] |
||||||
|
} |
||||||
|
set dupid "[dict get $fosdb_dupset $fosdb].$dbcount/[llength $siblingworkdirs]" |
||||||
|
} else { |
||||||
|
set dupid "" |
||||||
|
} |
||||||
|
if {$dbcount == 1} { |
||||||
|
sqlite3 fdb $fosdb |
||||||
|
set pname [lindex [fdb eval {select value from config where name = 'project-name'}] 0] |
||||||
|
set pcode [lindex [fdb eval {select value from config where name = 'project-code'}] 0] |
||||||
|
fdb close |
||||||
|
dict set fosdb_cache $fosdb [list name $pname code $pcode] |
||||||
|
} else { |
||||||
|
set info [dict get $fosdb_cache $fosdb] |
||||||
|
lassign $info _name pname _code pcode |
||||||
|
} |
||||||
|
lappend col_rowids $rowid |
||||||
|
lappend col_fnames $nm |
||||||
|
lappend col_dupids $dupid |
||||||
|
lappend col_pnames $pname |
||||||
|
lappend col_pcodes [string range $pcode 0 9] |
||||||
|
incr rowid |
||||||
|
} |
||||||
|
|
||||||
|
set col_states [list] |
||||||
|
set state_title "" |
||||||
|
#if only one set of fossil checkouts in the resultset - retrieve workingdir state for each co |
||||||
|
if {[llength [dict keys $fosdb_cache]] == 1} { |
||||||
|
puts stderr "Result is a single project - gathering file state for each checkout folder" |
||||||
|
set c_rev [list] |
||||||
|
set c_unchanged [list] |
||||||
|
set c_changed [list] |
||||||
|
set c_new [list] |
||||||
|
set c_missing [list] |
||||||
|
set c_extra [list] |
||||||
|
foreach wd $workdirs { |
||||||
|
set wd_state [punk::repo::workingdir_state $wd] |
||||||
|
set state_dict [punk::repo::workingdir_state_summary_dict $wd_state] |
||||||
|
lappend c_rev [string range [dict get $state_dict revision] 0 9] |
||||||
|
lappend c_unchanged [dict get $state_dict unchanged] |
||||||
|
lappend c_changed [dict get $state_dict changed] |
||||||
|
lappend c_new [dict get $state_dict new] |
||||||
|
lappend c_missing [dict get $state_dict missing] |
||||||
|
lappend c_extra [dict get $state_dict extra] |
||||||
|
puts -nonewline stderr "." |
||||||
|
} |
||||||
|
puts -nonewline stderr \n |
||||||
|
set t0 "Revision" |
||||||
|
set w0 [tcl::mathfunc::max {*}[lmap v [concat [list $t0] $c_rev] {string length $v}]] |
||||||
|
set c0 [string repeat " " $w0] |
||||||
|
set t1 "Unch" |
||||||
|
set w1 [tcl::mathfunc::max {*}[lmap v [concat [list $t1] $c_unchanged] {string length $v}]] |
||||||
|
set c1 [string repeat " " $w1] |
||||||
|
set t2 "Chgd" |
||||||
|
set w2 [tcl::mathfunc::max {*}[lmap v [concat [list $t2] $c_changed] {string length $v}]] |
||||||
|
set c2 [string repeat " " $w2] |
||||||
|
set t3 "New" |
||||||
|
set w3 [tcl::mathfunc::max {*}[lmap v [concat [list $t3] $c_new] {string length $v}]] |
||||||
|
set c3 [string repeat " " $w3] |
||||||
|
set t4 "Miss" |
||||||
|
set w4 [tcl::mathfunc::max {*}[lmap v [concat [list $t4] $c_missing] {string length $v}]] |
||||||
|
set c4 [string repeat " " $w4] |
||||||
|
set t5 "Extr" |
||||||
|
set w5 [tcl::mathfunc::max {*}[lmap v [concat [list $t5] $c_extra] {string length $v}]] |
||||||
|
set c5 [string repeat " " $w5] |
||||||
|
|
||||||
|
set state_title "[overtype::left $c0 $t0] [overtype::right $c1 $t1] [overtype::right $c2 $t2] [overtype::right $c3 $t3] [overtype::right $c4 $t4] [overtype::right $c5 $t5]" |
||||||
|
foreach r $c_rev u $c_unchanged c $c_changed n $c_new m $c_missing e $c_extra { |
||||||
|
lappend col_states "[overtype::left $c0 $r] [overtype::right $c1 $u] [overtype::right $c2 $c] [overtype::right $c3 $n] [overtype::right $c4 $m] [overtype::right $c5 $e]" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set msg "" |
||||||
|
if {$opt_cd} { |
||||||
|
set title0 "CD" |
||||||
|
} else { |
||||||
|
set title0 "" |
||||||
|
} |
||||||
|
set widest0 [tcl::mathfunc::max {*}[lmap v [concat [list $title0] $col_rowids] {punk::strlen $v}]] |
||||||
|
set col0 [string repeat " " $widest0] |
||||||
|
set title1 "Checkout dir" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $workdirs] {punk::strlen $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
set title2 "Db name" |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $col_fnames] {string length $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
set title3 "CO dup" |
||||||
|
set widest3 [tcl::mathfunc::max {*}[lmap v [concat [list $title3] $col_dupids] {string length $v}]] |
||||||
|
set col3 [string repeat " " $widest3] |
||||||
|
set title4 "Project Name" |
||||||
|
set widest4 [tcl::mathfunc::max {*}[lmap v [concat [list $title4] $col_pnames] {string length $v}]] |
||||||
|
set col4 [string repeat " " $widest4] |
||||||
|
set title5 "Project Code" |
||||||
|
set widest5 [tcl::mathfunc::max {*}[lmap v [concat [list $title5] $col_pcodes] {string length $v}]] |
||||||
|
set col5 [string repeat " " $widest5] |
||||||
|
|
||||||
|
set tablewidth [expr {$widest0 + 1 + $widest1 + 1 + $widest2 + 1 + $widest3 +1 + $widest4 + 1 + $widest5}] |
||||||
|
append msg "[overtype::right $col0 $title0] [overtype::left $col1 $title1] [overtype::left $col2 $title2] [overtype::left $col3 $title3] [overtype::left $col4 $title4] [overtype::left $col5 $title5]" |
||||||
|
|
||||||
|
if {[llength $col_states]} { |
||||||
|
set title6 $state_title |
||||||
|
set widest6 [tcl::mathfunc::max {*}[lmap v [concat [list $title6] $col_states] {string length $v}]] |
||||||
|
set col6 [string repeat " " $widest6] |
||||||
|
incr tablewidth [expr {$widest6 + 1}] |
||||||
|
append msg " [overtype::left $col6 $title6]" \n |
||||||
|
} else { |
||||||
|
append msg \n |
||||||
|
} |
||||||
|
append msg [string repeat "=" $tablewidth] \n |
||||||
|
|
||||||
|
if {[llength $col_states]} { |
||||||
|
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { |
||||||
|
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n |
||||||
|
} |
||||||
|
} else { |
||||||
|
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { |
||||||
|
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n |
||||||
|
} |
||||||
|
} |
||||||
|
set numrows [llength $col_rowids] |
||||||
|
if {$opt_cd && $numrows >= 1} { |
||||||
|
puts stdout $msg |
||||||
|
if {$numrows == 1} { |
||||||
|
set workingdir [lindex $workdirs 0] |
||||||
|
puts stdout "1 result. Changing dir to $workingdir" |
||||||
|
cd $workingdir |
||||||
|
return $workingdir |
||||||
|
} else { |
||||||
|
set answer [util::askuser "Change directory to working folder - select a number from 1 to [llength $col_rowids] or any other key to cancel."] |
||||||
|
if {[string trim $answer] in $col_rowids} { |
||||||
|
set index [expr {$answer - 1}] |
||||||
|
set workingdir [lindex $workdirs $index] |
||||||
|
cd $workingdir |
||||||
|
puts stdout [pmix stat] |
||||||
|
return $workingdir |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $msg |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval lib { |
||||||
|
#get project info only by opening the central confg-db |
||||||
|
#(will not have proper project-name etc) |
||||||
|
proc get_projects {{globlist {}} args} { |
||||||
|
if {![llength $globlist]} { |
||||||
|
set globlist [list *] |
||||||
|
} |
||||||
|
set fossil_prog [auto_execok fossil] |
||||||
|
|
||||||
|
set fossilinfo [exec {*}$fossil_prog info] ;#will give us the necessary config-db info whether in a project folder or not |
||||||
|
set matching_lines [punk::repo::grep {config-db:*} $fossilinfo] |
||||||
|
if {[llength $matching_lines] != 1} { |
||||||
|
puts stderr "Unable to find config-db info from fossil. Check your fossil installation." |
||||||
|
puts stderr "Fossil output was:" |
||||||
|
puts stderr "-------------" |
||||||
|
puts stderr "$fossilinfo" |
||||||
|
puts stderr "-------------" |
||||||
|
puts stderr "config-db info:" |
||||||
|
puts stderr "$matching_lines" |
||||||
|
return |
||||||
|
} |
||||||
|
set ln [lindex $matching_lines 0] |
||||||
|
set configdb [string trim [string range $ln [string length "config-db: "] end]] |
||||||
|
if {![file exists $configdb]} { |
||||||
|
error "config-db not found at path $configdb" |
||||||
|
} |
||||||
|
package require sqlite3 |
||||||
|
::sqlite3 fosconf $configdb |
||||||
|
#set testresult [fosconf eval {select name,value from global_config;}] |
||||||
|
#puts stderr $testresult |
||||||
|
set project_repos [fosconf eval {select name from global_config where name like 'repo:%';}] |
||||||
|
set paths_and_names [list] |
||||||
|
foreach pr $project_repos { |
||||||
|
set path [string trim [string range $pr 5 end]] |
||||||
|
set nm [file rootname [file tail $path]] |
||||||
|
set ckouts [fosconf eval {select name from global_config where value = $path;}] |
||||||
|
set checkout_paths [list] |
||||||
|
#strip "ckout:" |
||||||
|
foreach ck $ckouts { |
||||||
|
lappend checkout_paths [string trim [string range $ck 6 end]] |
||||||
|
} |
||||||
|
lappend paths_and_names [list $path $nm $checkout_paths] |
||||||
|
} |
||||||
|
set filtered_list [list] |
||||||
|
foreach glob $globlist { |
||||||
|
set matches [lsearch -all -inline -index 1 $paths_and_names $glob] |
||||||
|
foreach m $matches { |
||||||
|
if {$m ni $filtered_list} { |
||||||
|
lappend filtered_list $m |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set projects [lsort -index 1 $filtered_list] |
||||||
|
return $projects |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::project [namespace eval punk::mix::commandset::project { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -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 <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::repo 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::repo { |
||||||
|
namespace export * |
||||||
|
proc tickets {{project ""}} { |
||||||
|
set result "" |
||||||
|
if {[string length $project]} { |
||||||
|
puts stderr "project status unimplemented" |
||||||
|
return |
||||||
|
} |
||||||
|
set active_dir [pwd] |
||||||
|
append result "Retrieving top 10 tickets only (for more, use fossil timeline -n <int> -t t)" \n |
||||||
|
append result [exec fossil timeline -n 10 -t t] |
||||||
|
|
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
proc fossilize { args} { |
||||||
|
#check if project already managed by fossil.. initialise and check in if not. |
||||||
|
puts stderr "unimplemented" |
||||||
|
} |
||||||
|
|
||||||
|
proc unfossilize {projectname args} { |
||||||
|
#remove/archive .fossil |
||||||
|
puts stderr "unimplemented" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::repo [namespace eval punk::mix::commandset::repo { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,600 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::commandset::scriptwrap 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
package require punk::mix |
||||||
|
package require punk::mix::base |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::commandset::scriptwrap { |
||||||
|
namespace export * |
||||||
|
|
||||||
|
|
||||||
|
#scriptpath allows templates command to use same custom template set as when multishell pointed to a filepath |
||||||
|
#it may or may not be within a project |
||||||
|
#by using the same folder or path, the same project root will be discovered. REVIEW. |
||||||
|
proc templates_dict {args} { |
||||||
|
set defaults [list -scriptpath ""] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
set opt_scriptpath [dict get $opts -scriptpath] |
||||||
|
|
||||||
|
set wrapper_folders [lib::get_wrapper_folders $opt_scriptpath] |
||||||
|
|
||||||
|
set wrapper_templates [list] |
||||||
|
foreach fld $wrapper_folders { |
||||||
|
set templates [glob -nocomplain -dir $fld -type f *] |
||||||
|
foreach tf $templates { |
||||||
|
if {[string match ignore* $tf]} { |
||||||
|
continue |
||||||
|
} |
||||||
|
set ext [file extension $tf] |
||||||
|
if {$ext in [list "" ".bat" ".cmd" ".sh"]} { |
||||||
|
lappend wrapper_templates $tf |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set tdict [dict create] |
||||||
|
set seen_dict [dict create] |
||||||
|
foreach fullpath $wrapper_templates { |
||||||
|
set ftail [file tail $fullpath] |
||||||
|
if {![dict exists $seen_dict $ftail]} { |
||||||
|
dict set seen_dict $ftail 1 |
||||||
|
dict set tdict $ftail $fullpath ; #first seen of filename gets no number |
||||||
|
} else { |
||||||
|
set n [dict get $seen_dict $ftail] |
||||||
|
incr n |
||||||
|
dict incr seen_dict $ftail |
||||||
|
dict set tdict ${ftail}.$n $fullpath |
||||||
|
} |
||||||
|
} |
||||||
|
return $tdict |
||||||
|
} |
||||||
|
proc templates {args} { |
||||||
|
package require overtype |
||||||
|
set tdict [templates_dict {*}$args] |
||||||
|
|
||||||
|
|
||||||
|
set paths [dict values $tdict] |
||||||
|
set names [dict keys $tdict] |
||||||
|
|
||||||
|
set title1 "Path" |
||||||
|
set widest1 [tcl::mathfunc::max {*}[lmap v [concat [list $title1] $paths] {punk::strlen $v}]] |
||||||
|
set col1 [string repeat " " $widest1] |
||||||
|
|
||||||
|
set title2 "Template Name" |
||||||
|
set widest2 [tcl::mathfunc::max {*}[lmap v [concat [list $title2] $names] {punk::strlen $v}]] |
||||||
|
set col2 [string repeat " " $widest2] |
||||||
|
|
||||||
|
set tablewidth [expr {$widest1 + 1 + $widest2}] |
||||||
|
set table "" |
||||||
|
append table [string repeat - $tablewidth] \n |
||||||
|
append table "[overtype::left $col1 $title1] [overtype::left $col2 $title2]" \n |
||||||
|
append table [string repeat - $tablewidth] \n |
||||||
|
|
||||||
|
foreach p $paths n $names { |
||||||
|
append table "[overtype::left $col1 $p] [overtype::left $col2 $n]" \n |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return $table |
||||||
|
} |
||||||
|
#specific filepath to just wrap one script at the tcl-payload or xxx-payload-pre-tcl site |
||||||
|
#scriptset name to substiture multiple scriptset.xxx files at the default locations - or as specified in scriptset.wrapconf |
||||||
|
proc multishell {filepath_or_scriptset args} { |
||||||
|
set defaults [list -askme 1 -template \uFFFF] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
set opt_askme [dict get $opts -askme] |
||||||
|
set opt_template [dict get $opts -template] |
||||||
|
set ext [file extension $filepath_or_scriptset] |
||||||
|
set startdir [pwd] |
||||||
|
|
||||||
|
set usage "" |
||||||
|
append usage "Use directly with the script file to wrap, or supply the name of a scriptset" \n |
||||||
|
append usage "The scriptset name will be used to search for yourname.sh|tcl|ps1 or names as you specify in yourname.wrapconfig if it exists" \n |
||||||
|
append usage "If no template is specified in a .wrapconfig and no -template argument is supplied, it will default to punk-multishell.cmd" \n |
||||||
|
if {![string length $filepath_or_scriptset]} { |
||||||
|
puts stderr "No filepath_or_scriptset specified" |
||||||
|
puts stderr $usage |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#first check if relative or absolute path matches a file |
||||||
|
if {[file pathtype $filepath_or_scriptset] eq "absolute"} { |
||||||
|
set specified_path $filepath_or_scriptset |
||||||
|
} else { |
||||||
|
set specified_path [file join $startdir $filepath_or_scriptset] |
||||||
|
} |
||||||
|
|
||||||
|
set ext [string trim [file extension $filepath_or_scriptset] .] |
||||||
|
set allowed_extensions [list wrapconfig tcl ps1 sh bash] |
||||||
|
#set allowed_extensions [list tcl] |
||||||
|
set found_script 0 |
||||||
|
if {[file exists $specified_path]} { |
||||||
|
set found_script 1 |
||||||
|
} else { |
||||||
|
foreach e $allowed_extensions { |
||||||
|
if {[file exists $filepath_or_scriptset.$e]} { |
||||||
|
set found_script 1 |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#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 {[file type $specified_path] eq "file"} { |
||||||
|
set specified_root [file dirname $specified_path] |
||||||
|
set pathinfo [punk::repo::find_repos [file dirname $specified_path]] |
||||||
|
set projectroot [dict get $pathinfo closest] |
||||||
|
if {[string length $projectroot]} { |
||||||
|
#use the specified files folder - but use the main scriptapps/wrappers folder if specified one has no wrappers subfolder |
||||||
|
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 { |
||||||
|
#outside of any project |
||||||
|
set scriptroot [file dirname $specified_path] |
||||||
|
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 { |
||||||
|
set pathinfo [punk::repo::find_repos $startdir] |
||||||
|
set projectroot [dict get $pathinfo closest] |
||||||
|
if {[string length $projectroot]} { |
||||||
|
if {[llength [file split $filepath_or_scriptset]] > 1} { |
||||||
|
puts stderr "filepath_or_scriptset looks like a path - but doesn't seem to point to a file" |
||||||
|
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 |
||||||
|
} else { |
||||||
|
#we've already ruled out empty string - so must have a single element representing scriptset - possibly with file extension |
||||||
|
set scriptroot $projectroot/src/scriptapps |
||||||
|
set customwrapper_folder $projectroot/src/scriptapps/wrappers |
||||||
|
#check something matches the scriptset.. |
||||||
|
set something_found "" |
||||||
|
if {[file exists $scriptroot/$scriptset]} { |
||||||
|
set found_script 1 |
||||||
|
set something_found $scriptroot/$scriptset ;#extensionless file - that's ok too |
||||||
|
} else { |
||||||
|
foreach e $allowed_extensions { |
||||||
|
if {[file exists $scriptroot/$scriptset.$e]} { |
||||||
|
set found_script 1 |
||||||
|
set something_found $scriptroot/$scriptset.$e |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if {!$found_script} { |
||||||
|
puts stderr "Searched within $scriptroot" |
||||||
|
puts stderr "Unable to find a file matching $scriptset or one of the extensions: $allowed_extensions" |
||||||
|
puts stderr $usage |
||||||
|
return false |
||||||
|
} else { |
||||||
|
if {[file pathtype $something_found] ne "file"} { |
||||||
|
puts stderr "wrap_in_multishell doesn't currently support a directory as the path." |
||||||
|
puts stderr $usage |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
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" |
||||||
|
puts stderr $usage |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
#assert - customwrapper_folder var exists - but might be empty |
||||||
|
|
||||||
|
|
||||||
|
if {[string length $ext]} { |
||||||
|
#If there was an explicitly supplied extension - then that file should exist |
||||||
|
if {![file exists $scriptroot/$scriptset.$ext]} { |
||||||
|
puts stderr "Explicit extension .$ext was supplied - but matching file not found." |
||||||
|
puts stderr $usage |
||||||
|
return false |
||||||
|
} else { |
||||||
|
if {$ext eq "wrapconfig"} { |
||||||
|
set process_extensions ALLFOUNDORCONFIGURED |
||||||
|
} else { |
||||||
|
set process_extensions $ext |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
#no explicit extension - process all for scriptset |
||||||
|
set process_extensions ALLFOUNDORCONFIGURED |
||||||
|
} |
||||||
|
#process_extensions - either a single one - or all found or as per .wrapconfig |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set libwrapper_folder_default [file join [::punk::mix::base::lib::mix_templates_dir] utility scriptappwrappers] |
||||||
|
if {$opt_template eq "\uFFFF"} { |
||||||
|
set templatename punk-multishell.cmd |
||||||
|
} |
||||||
|
if {[string length $customwrapper_folder] && [file exists [file join $customwrapper_folder $templatename] ]} { |
||||||
|
set wrapper_template [file join $customwrapper_folder $templatename] |
||||||
|
} else { |
||||||
|
set wrapper_template [file join $libwrapper_folder_default $templatename] |
||||||
|
} |
||||||
|
|
||||||
|
if {![file exists $wrapper_template]} { |
||||||
|
error "wrap_in_multishell: unable to find multishell template at $wrapper_template" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#todo |
||||||
|
#output_file extension depends on the template being used.. |
||||||
|
|
||||||
|
|
||||||
|
set output_file $scriptset.cmd |
||||||
|
if {[file exists $output_file]} { |
||||||
|
error "wrap_in_multishell: target file $output_file already exists.. aborting" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set fdt [open $wrapper_template r] |
||||||
|
fconfigure $fdt -translation binary |
||||||
|
set template_data [read $fdt] |
||||||
|
close $fdt |
||||||
|
puts stdout "Read [string length $template_data] bytes of template data.." |
||||||
|
set template_lines [split $template_data \n] |
||||||
|
puts stdout "Displaying first 3 lines of template between dashed lines..." |
||||||
|
puts stdout "-----------------------------------------------" |
||||||
|
foreach ln [lrange $template_lines 0 3] { |
||||||
|
puts stdout $ln |
||||||
|
} |
||||||
|
puts stdout "-----------------------------------------------\n" |
||||||
|
#foreach ln $template_lines { |
||||||
|
#} |
||||||
|
|
||||||
|
set list_input_files [list] |
||||||
|
if {$process_extensions eq "ALLFOUNDORCONFIGURED"} { |
||||||
|
#todo - look for .wrapconfig or all extensions for the scriptset |
||||||
|
puts stderr "Sorry - only single input file supported - implementation incomplete" |
||||||
|
return false |
||||||
|
} else { |
||||||
|
lappend list_input_files $scriptroot/$scriptset.$ext |
||||||
|
} |
||||||
|
|
||||||
|
#todo - split template at each <ext-payload> etc marker and build a dict of parts |
||||||
|
|
||||||
|
|
||||||
|
#hack - process one input |
||||||
|
set filepath [lindex $list_input_files 0] |
||||||
|
|
||||||
|
set fdscript [open $filepath r] |
||||||
|
fconfigure $fdscript -translation binary |
||||||
|
set script_data [read $fdscript] |
||||||
|
close $fdscript |
||||||
|
puts stdout "Read [string length $script_data] bytes of template data.." |
||||||
|
set script_lines [split $script_data \n] |
||||||
|
puts stdout "Displaying first 3 lines of your script between dashed lines..." |
||||||
|
puts stdout "-----------------------------------------------" |
||||||
|
foreach ln [lrange $script_lines 0 3] { |
||||||
|
puts stdout $ln |
||||||
|
} |
||||||
|
puts stdout "-----------------------------------------------\n" |
||||||
|
if {$opt_askme} { |
||||||
|
puts stdout "Target for above data is '$output_file'" |
||||||
|
set answer [util::askuser "Does this look correct? Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y or y to proceed) use -askme 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set start_idx 0 |
||||||
|
set end_idx 0 |
||||||
|
set line_idx 0 |
||||||
|
set existing_payload [list] |
||||||
|
foreach ln $template_lines { |
||||||
|
|
||||||
|
if {[string match "#<tcl-payload>*" $ln]} { |
||||||
|
set start_idx $line_idx |
||||||
|
} elseif {[string match "#</tcl-payload>*" $ln]} { |
||||||
|
set end_idx $line_idx |
||||||
|
break |
||||||
|
} elseif {$start_idx > 0} { |
||||||
|
if {$end_idx > 0} { |
||||||
|
lappend existing_payload [string trim $ln] |
||||||
|
} |
||||||
|
} else { |
||||||
|
|
||||||
|
} |
||||||
|
incr line_idx |
||||||
|
} |
||||||
|
if {($start_idx == 0) || ($end_idx == 0)} { |
||||||
|
error "wrap_in_multishell was unable to find payload area in template marked with #<tcl-payload> and #</tcl-payload> on separate lines" |
||||||
|
} |
||||||
|
set existing_string [join $existing_payload \n] |
||||||
|
if {[string length [string trim $existing_string]]} { |
||||||
|
puts stdout "EXISTING PAYLOAD!!" |
||||||
|
puts stdout "-----------------------------------------------\n" |
||||||
|
puts stdout $existing_string |
||||||
|
puts stdout "-----------------------------------------------\n" |
||||||
|
error "wrap_in_multishell found existing payload.. aborting." |
||||||
|
#todo - allow overwrite only in files outside of punkshell distribution? |
||||||
|
if 0 { |
||||||
|
puts stderr "Found existing payload.. overwrite?" |
||||||
|
if {$opt_askme} { |
||||||
|
set answer [util::askuser "Are you sure you want to replace the tcl payload shown above? Y|N"] |
||||||
|
if {[string tolower $answer] ne "y"} { |
||||||
|
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -askme 0 to avoid prompts." |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set tpl_head_lines [lrange $template_lines 0 $start_idx] ;#include tag line |
||||||
|
set tpl_tail_lines [lrange $template_lines $end_idx end] |
||||||
|
set newscript [join $tpl_head_lines \n]\n[join $script_lines \n]\n[join $tpl_tail_lines \n] |
||||||
|
puts stdout "New script is [string length $newscript] bytes" |
||||||
|
puts stdout $newscript |
||||||
|
set fdtarget [open $output_file w] |
||||||
|
fconfigure $fdtarget -translation binary |
||||||
|
puts -nonewline $fdtarget $newscript |
||||||
|
close $fdtarget |
||||||
|
puts stdout "Wrote script file at $output_file" |
||||||
|
puts stdout "-done-" |
||||||
|
return $output_file |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval lib { |
||||||
|
|
||||||
|
#get_wrapper_folders |
||||||
|
# scriptpath - file or folder |
||||||
|
# It represents the base point from which to search for /wrapper folders either directly above the scriptpath or in the containing project if any |
||||||
|
# The cwd will also be searched for /wrapper folder and project - but with lower precedence in the resultset (later in list) |
||||||
|
proc get_wrapper_folders {{scriptpath ""}} { |
||||||
|
set wrapper_folders [list] |
||||||
|
if {$scriptpath ne ""} { |
||||||
|
if {[file type $scriptpath] eq "file"} { |
||||||
|
set searchbase [file dirname $scriptpath] |
||||||
|
} else { |
||||||
|
set searchbase $scriptpath |
||||||
|
} |
||||||
|
if {[file isdirectory [file join $searchbase wrappers]]} { |
||||||
|
lappend wrapper_folders [file join $searchbase wrappers] |
||||||
|
} |
||||||
|
set pathinfo [punk::repo::find_repos $searchbase] |
||||||
|
set scriptpath_projectroot [dict get $pathinfo closest] |
||||||
|
if {$scriptpath_projectroot ne ""} { |
||||||
|
set fld [file join $scriptpath_projectroot src/scriptapps/wrappers] |
||||||
|
if {[file isdirectory $fld]} { |
||||||
|
if {$fld ni $wrapper_folders} { |
||||||
|
lappend wrapper_folders $fld |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set searchbase [pwd] |
||||||
|
set fld [file join $searchbase wrappers] |
||||||
|
if {[file isdirectory $fld]} { |
||||||
|
if {$fld ni $wrapper_folders} { |
||||||
|
lappend wrapper_folders $fld |
||||||
|
} |
||||||
|
} |
||||||
|
set pathinfo [punk::repo::find_repos $searchbase] |
||||||
|
set pwd_projectroot [dict get $pathinfo closest] |
||||||
|
if {$pwd_projectroot ne ""} { |
||||||
|
set fld [file join $pwd_projectroot src/scriptapps/wrappers] |
||||||
|
if {[file isdirectory $fld]} { |
||||||
|
if {$fld ni $wrapper_folders} { |
||||||
|
lappend wrapper_folders $fld |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set fld [file join [::punk::mix::base::lib::mix_templates_dir] utility scriptappwrappers] |
||||||
|
if {[file isdirectory $fld]} { |
||||||
|
if {$fld ni $wrapper_folders} { |
||||||
|
lappend wrapper_folders $fld |
||||||
|
} |
||||||
|
} |
||||||
|
return $wrapper_folders |
||||||
|
} |
||||||
|
proc _scriptapp_tag_from_line {line} { |
||||||
|
set result [list istag 0 raw ""] ;#default assumption. All |
||||||
|
#---- |
||||||
|
set startc [string first "#" $line] ;#tags must be commented |
||||||
|
#todo - review. next line is valid - note # doesn't have to be the only one before <tagname> |
||||||
|
# @REM # etc < blah # <tagname> etc |
||||||
|
#--- |
||||||
|
#fix - we should use a regexp on at least <tagname> </tagname> <tagname/> and only catch tagname without whitespace |
||||||
|
regexp {(\s*).*} $line _ln indent ;#will match on empty line, whitespace only line - or anything really. |
||||||
|
set indent [string map [list \t " "] $indent] ;#opinionated I guess - but need to normalize to something. The spec is that spaces should be used anyway. |
||||||
|
dict set result indent [string length $indent] |
||||||
|
set starttag [string first "<" $line] |
||||||
|
set pretag [string range $line $startc $starttag-1] |
||||||
|
if {[string match "*>*" $pretag]} { |
||||||
|
return [list istag 0 raw $line reason pretag_contents] |
||||||
|
} |
||||||
|
set closetag [string first ">" $line] |
||||||
|
set inelement [string range $line $starttag+1 $closetag-1] |
||||||
|
if {[string match "*<*" $inelement]} { |
||||||
|
return [list istag 0 raw $line reason tag_malformed_angles] |
||||||
|
} |
||||||
|
set elementchars [split $inelement ""] |
||||||
|
set numslashes [llength [lsearch -all $elementchars "/"]] |
||||||
|
if {$numslashes == 0} { |
||||||
|
dict set result type "open" |
||||||
|
} elseif {$numslashes == 1} { |
||||||
|
if {[lindex $elementchars 0] eq "/"} { |
||||||
|
dict set result type "close" |
||||||
|
} elseif {[lindex $elementchars end] eq "/"} { |
||||||
|
dict set result type "openclose" |
||||||
|
} else { |
||||||
|
return [list istag 0 raw $line reason tag_malformed_slashes] |
||||||
|
} |
||||||
|
} else { |
||||||
|
return [list istag 0 raw $line reason tag_malformed_extraslashes] |
||||||
|
} |
||||||
|
if {[dict get $result type] eq "open"} { |
||||||
|
dict set result name $inelement |
||||||
|
} elseif {[dict get $result type] eq "close"} { |
||||||
|
dict set result name [string range $inelement 1 end] |
||||||
|
} else { |
||||||
|
dict set result name [string range $inelement 0 end-1] |
||||||
|
} |
||||||
|
dict set result istag 1 |
||||||
|
dict set result raw $line |
||||||
|
return $result |
||||||
|
} |
||||||
|
|
||||||
|
#get all \n#<something>\n ...\n#</something> data - where number of intervening newlines is at least one (and whitespace and/or other data can precede #) |
||||||
|
#we don't verify 'something' against known tags - as custom templates can have own tags |
||||||
|
#An openclose tag #<xxx/> is used to substitute a specific line in its entirety - but the tag *must* remain in the line |
||||||
|
# |
||||||
|
#e.g for the line: |
||||||
|
# @set "nextshell=pwsh" & :: #<batch-nextshell-line/> |
||||||
|
#The .wrapconfig might contain |
||||||
|
# tag <batch-nextshell-line> line {@set "nextshell=tclsh" & :: @<batch-nextshell-line/>} |
||||||
|
# |
||||||
|
proc scriptapp_wrapper_get_tags {wrapperdata} { |
||||||
|
set wrapperdata [string map [list \r\n \n] $wrapperdata] |
||||||
|
set lines [split $wrapperdata \n] |
||||||
|
#set tags_in_data [dict create];#active tags - list of lines accumulating. supports nested tags |
||||||
|
set status 0 |
||||||
|
set tags [dict create] |
||||||
|
set errors [list] |
||||||
|
set errortags [dict create] ;#mark names invalid on first error so that more than 2 tags can't obscure config problem |
||||||
|
set linenum 1 ;#editors and other utils use 1-based indexing when referencing files - we should too to avoid confusion, despite it being less natural for lindex operations on the result. |
||||||
|
foreach ln $lines { |
||||||
|
set lntrim [string trim $ln] |
||||||
|
if {![string length $lntrim]} { |
||||||
|
incr linenum |
||||||
|
continue |
||||||
|
} |
||||||
|
if {[string match "*#*<*>*" $lntrim]} { |
||||||
|
set taginfo [_scriptapp_tag_from_line $ln] ;#use untrimmed line - to get indent |
||||||
|
if {[dict get $taginfo istag]} { |
||||||
|
set nm [dict get $taginfo name] |
||||||
|
if {[dict exists $errortags $nm]} { |
||||||
|
#tag is already in error condition - |
||||||
|
} else { |
||||||
|
set tp [dict get $taginfo type] ;# type singular - related to just one line |
||||||
|
#set raw [dict get $taginfo raw] #equivalent to $ln |
||||||
|
if {[dict exists $tags $nm]} { |
||||||
|
#already seen tag name |
||||||
|
#tags dict has types key *plural* - need to track whether we have type open and type close (or openclose for self-closing tags) |
||||||
|
if {[dict get $tags $nm types] ne "open"} { |
||||||
|
lappend errors "line: $linenum tag $nm encountered type $tp after existing type [dict get $tags $nm types]" |
||||||
|
dict incr errortags $nm |
||||||
|
} else { |
||||||
|
#we already have open - expect only close |
||||||
|
if {$tp ne "close"} { |
||||||
|
lappend errors "line: $linenum tag $nm encountered type $tp after existing type [dict get $tags $nm types]" |
||||||
|
dict incr errortags $nm |
||||||
|
} else { |
||||||
|
#close after open |
||||||
|
dict set tags $nm types [list open close] |
||||||
|
dict set tags $nm end $linenum |
||||||
|
set taglines [dict get $tags $nm taglines] |
||||||
|
if {[llength $taglines] != 1} { |
||||||
|
error "Unexpected result when closing tag $nm. Existing taglines length not 1." |
||||||
|
} |
||||||
|
dict set tags $nm taglines [concat $taglines $ln] |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
#first seen of tag name |
||||||
|
if {$tp eq "close"} { |
||||||
|
lappend errors "line: $linenum tag $nm encountered type $p close first" |
||||||
|
dict incr errortags $nm |
||||||
|
} else { |
||||||
|
dict set tags $nm types $tp |
||||||
|
dict set tags $nm indent [dict get $taginfo indent] |
||||||
|
if {$tp eq "open"} { |
||||||
|
dict set tags $nm start $linenum |
||||||
|
dict set tags $nm taglines [list $ln] ;#first entry - another will be added on encountering matching closing tag |
||||||
|
} elseif {$tp eq "openclose"} { |
||||||
|
dict set tags $nm start $linenum |
||||||
|
dict set tags $nm end $linenum |
||||||
|
dict set tags $nm taglines [list $ln] ;#single entry is final result for self-closing tag |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
#looks like it should be a tag.. but failed to even parse for some reason.. just add to errorlist |
||||||
|
lappend errors "line: $linenum tag parse failure reason: [dict get $taginfo reason] raw line: [dict get $taginfo raw]" |
||||||
|
} |
||||||
|
} |
||||||
|
#whether the line is tag or not append to any tags_in_data |
||||||
|
#foreach t [dict keys $tags_in_data] { |
||||||
|
# dict lappend tags_in_data $t $ln ;#accumulate raw lines - written to the tag entry in tags only on encountering a closing tag, then removed from tags_in_data |
||||||
|
#} |
||||||
|
incr linenum |
||||||
|
} |
||||||
|
#assert [expr {$linenum -1 == [llength $lines]}] |
||||||
|
if {[llength $errors]} { |
||||||
|
set status 0 |
||||||
|
} else { |
||||||
|
set status 1 |
||||||
|
} |
||||||
|
if {$linenum == 0} { |
||||||
|
|
||||||
|
} |
||||||
|
return [dict create ok $status linecount [llength $lines] data $tags errors $errors] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::commandset::scriptwrap [namespace eval punk::mix::commandset::scriptwrap { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -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 |
@ -0,0 +1,7 @@ |
|||||||
|
src |
||||||
|
src/vendorlib |
||||||
|
src/vendormodules |
||||||
|
src/modules |
||||||
|
src/lib |
||||||
|
lib |
||||||
|
modules |
@ -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 |
@ -0,0 +1,39 @@ |
|||||||
|
|
||||||
|
/bin/ |
||||||
|
/lib/ |
||||||
|
#The directory for compiled/built Tcl modules |
||||||
|
/modules/ |
||||||
|
/vendorbuilds/ |
||||||
|
|
||||||
|
#Temporary files e.g from tests |
||||||
|
/tmp/ |
||||||
|
|
||||||
|
/logs/ |
||||||
|
**/_aside/ |
||||||
|
**/_build/ |
||||||
|
scratch* |
||||||
|
|
||||||
|
#Built documentation |
||||||
|
/html/ |
||||||
|
/man/ |
||||||
|
/md/ |
||||||
|
/doc/ |
||||||
|
|
||||||
|
/test* |
||||||
|
|
||||||
|
|
||||||
|
#Built tclkits (if any) |
||||||
|
punk*.exe |
||||||
|
tcl*.exe |
||||||
|
|
||||||
|
#ignore fossil database files (but keep .fossil-settings and .fossil-custom in repository even if fossil not being used at your site) |
||||||
|
_FOSSIL_ |
||||||
|
.fos |
||||||
|
.fslckout |
||||||
|
*.fossil |
||||||
|
|
||||||
|
#miscellaneous editor files etc |
||||||
|
*.swp |
||||||
|
|
||||||
|
|
||||||
|
todo.txt |
@ -0,0 +1,13 @@ |
|||||||
|
%project% |
||||||
|
============================== |
||||||
|
|
||||||
|
+ |
||||||
|
+ |
||||||
|
|
||||||
|
|
||||||
|
About |
||||||
|
------------------------------ |
||||||
|
|
||||||
|
+ |
||||||
|
+ |
||||||
|
+ |
@ -0,0 +1,11 @@ |
|||||||
|
Tcl Module Source files for the project. |
||||||
|
Consider using the punkshell pmix facility to create and manage these. |
||||||
|
|
||||||
|
pmix::newmodule <name> will create a basic .tm module template and assist in versioning. |
||||||
|
|
||||||
|
Tcl modules can be namespaced. |
||||||
|
For example |
||||||
|
> pmix::newmodule mymodule::utils |
||||||
|
will create the new module under src/modules/mymodule/utils |
||||||
|
|
||||||
|
|
@ -0,0 +1,51 @@ |
|||||||
|
#======================================================= |
||||||
|
#configuration data for download and build of buildsuite |
||||||
|
#======================================================= |
||||||
|
#windows build mechanism: mingw64 -ucrt64 gcc |
||||||
|
#other platforms build mechanism: gcc |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------- |
||||||
|
set buildprefix <PROJECTDIR>/vendorbuild/samplesuite1 |
||||||
|
set basemakeflags [list -j 2] |
||||||
|
set baseconfigflags [list --enable-64bit --prefix=$buildprefix] |
||||||
|
set repofolder ~/.fossils |
||||||
|
#------------------------------------------------------- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SOURCEDOWNLOAD -project tcl\ |
||||||
|
-mechanism fossil\ |
||||||
|
-localrepo tcl.fossil\ |
||||||
|
-branch core-8\ |
||||||
|
-remote https://core.tcl-lang.org/tcl |
||||||
|
|
||||||
|
#etc. |
||||||
|
#SOURCEDOWNLOAD tk... |
||||||
|
#SOURCEDOWNLOAD thread... |
||||||
|
|
||||||
|
#SOURCEDOWNLOAD -project critcl\ |
||||||
|
-mechanism git\ |
||||||
|
-branch master\ |
||||||
|
-remote http://github.com/andreas-kupries/critcl |
||||||
|
|
||||||
|
#SOURCEDOWNLOAD -project tclbench\ |
||||||
|
-mechanism fossil\ |
||||||
|
-remote https://core.tcl-lang.org/tclbench |
||||||
|
|
||||||
|
#CONFIGSTART tcl <SOURCEBASE>/win |
||||||
|
|
||||||
|
#CONFIGSTART tk <SOURCEBASE>/win |
||||||
|
#EXTRACONFIG tk --with-tcl=$buildprefix/lib --with-tclinclude=$buildprefix/include |
||||||
|
|
||||||
|
|
||||||
|
#CONFIGSTART thread ??? |
||||||
|
#EXTRACONFIG thread --with-tcl=$buildprefix/lib --with-tclinclude=$buildprefix/include |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,65 +0,0 @@ |
|||||||
|
|
||||||
namespace eval punk::clitemplate { |
|
||||||
|
|
||||||
proc runcli {args} { |
|
||||||
if {![llength $args]} { |
|
||||||
tailcall punk::clitemplate::clicommands help |
|
||||||
} else { |
|
||||||
tailcall punk::clitemplate::clicommands {*}$args |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
namespace eval punk::clitemplate::clicommands { |
|
||||||
variable last_alias "" |
|
||||||
namespace export help |
|
||||||
namespace ensemble create |
|
||||||
namespace ensemble configure [namespace current] -unknown punk::clitemplate::clicommands::_unknown |
|
||||||
|
|
||||||
proc set_alias {cmdname} { |
|
||||||
variable last_alias |
|
||||||
set last_alias $cmdname |
|
||||||
uplevel #0 [list interp alias {} $cmdname {} punk::clitemplate::runcli] |
|
||||||
} |
|
||||||
proc _unknown {ns args} { |
|
||||||
punk::clitemplate::clicommands::help {*}$args |
|
||||||
} |
|
||||||
|
|
||||||
proc help {args} { |
|
||||||
#' **%ensemblecommand% help** *args* |
|
||||||
#' |
|
||||||
#' Help for ensemble commands in the command line interface |
|
||||||
#' |
|
||||||
#' |
|
||||||
#' Arguments: |
|
||||||
#' |
|
||||||
#' * args - first word of args is the helptopic requested - usually a command name |
|
||||||
#' - calling help with no arguments will list available commands |
|
||||||
#' |
|
||||||
#' Returns: help text (text) |
|
||||||
#' |
|
||||||
#' Examples: |
|
||||||
#' |
|
||||||
#' ``` |
|
||||||
#' %ensemblecommand% help <commandname> |
|
||||||
#' ``` |
|
||||||
#' |
|
||||||
#' |
|
||||||
|
|
||||||
|
|
||||||
set commands [namespace export] |
|
||||||
set output "" |
|
||||||
append output "commands:\n |
|
||||||
foreach cmd $commands { |
|
||||||
append output " $cmd" |
|
||||||
} |
|
||||||
return $output |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
package provide punk::clitemplate [namespace eval punk::clitemplate { |
|
||||||
variable version |
|
||||||
set version 0.1 |
|
||||||
}] |
|
@ -0,0 +1,49 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application %pkg% 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
foreach base [tcl::tm::list] { |
||||||
|
set nsprefix "";#in case sourced directly and not in any of the .tm paths |
||||||
|
if {[string match -nocase ${base}* [info script]]} { |
||||||
|
set nsprefix [string trimleft [join [lrange [file split [string range [info script] [string length $base]+1 end]] 0 end-1] ::]:: ::] |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
namespace eval [lassign [split [file rootname [file tail [info script] ]] -] pkgtail verparts]${nsprefix}$pkgtail { |
||||||
|
#-------------------------------------- |
||||||
|
#Do not put any 'package require' statements above this block. (globals nsprefix,pkgtail,verparts still set) |
||||||
|
variable pkg "${::nsprefix}${::pkgtail}[unset ::nsprefix; unset ::pkgtail]" |
||||||
|
variable version [join $::verparts -][unset ::verparts] |
||||||
|
#-------------------------------------- |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace eval [namespace current]::lib { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
uplevel #0 [list package provide $pkg $version] |
||||||
|
} |
||||||
|
return |
||||||
|
|
@ -0,0 +1,63 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) %year% |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application %pkg% 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license %license% |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
package require punk::mix::util |
||||||
|
|
||||||
|
namespace eval %pkg% { |
||||||
|
namespace ensemble create |
||||||
|
#package require punk::overlay |
||||||
|
#punk::overlay::import_commandset debug. ::punk:mix::commandset::debug |
||||||
|
|
||||||
|
|
||||||
|
proc help {args} { |
||||||
|
set basehelp [punk::mix::base help {*}$args] |
||||||
|
return $basehelp |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval %pkg%::lib { |
||||||
|
namespace path ::punk::mix::util ;#askuser, do_in_path, foreach-file etc |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval %pkg% { |
||||||
|
proc _cli {args} { |
||||||
|
#don't use tailcall - base uses info level to determine caller |
||||||
|
::punk::mix::base::_cli {*}$args |
||||||
|
} |
||||||
|
variable default_command help |
||||||
|
package require punk::mix::base |
||||||
|
package require punk::overlay |
||||||
|
punk::overlay::custom_from_base [namespace current] ::punk::mix::base |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide %pkg% [namespace eval %pkg% { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
||||||
|
|
||||||
|
|
@ -0,0 +1,45 @@ |
|||||||
|
# -*- 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 |
@ -0,0 +1,426 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::mix::util 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
namespace eval punk::mix::util { |
||||||
|
variable has_winpath 0 |
||||||
|
} |
||||||
|
|
||||||
|
if {"windows" eq $::tcl_platform(platform)} { |
||||||
|
if {![catch {package require punk::winpath}]} { |
||||||
|
set punk::mix::util::has_winpath 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::mix::util { |
||||||
|
variable tmpfile_counter 0 ;#additional tmpfile collision avoidance |
||||||
|
|
||||||
|
namespace export * |
||||||
|
|
||||||
|
|
||||||
|
proc fcat {args} { |
||||||
|
variable has_winpath |
||||||
|
|
||||||
|
if {$::tcl_platform(platform) ne "windows"} { |
||||||
|
return [fileutil::cat {*}$args] |
||||||
|
} |
||||||
|
|
||||||
|
set knownopts [list -eofchar -translation -encoding --] |
||||||
|
set last_opt 0 |
||||||
|
for {set i 0} {$i < [llength $args]} {incr i} { |
||||||
|
set ival [lindex $args $i] |
||||||
|
#puts stdout "i:$i a: $ival known: [expr {$ival in $knownopts}]" |
||||||
|
if {$ival eq "--"} { |
||||||
|
set last_opt $i |
||||||
|
break |
||||||
|
} else { |
||||||
|
if {$ival in $knownopts} { |
||||||
|
#puts ">known at $i : [lindex $args $i]" |
||||||
|
if {($i % 2) != 0} { |
||||||
|
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." |
||||||
|
} |
||||||
|
incr i |
||||||
|
set last_opt $i |
||||||
|
} else { |
||||||
|
set last_opt [expr {$i - 1}] |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set first_non_opt [expr {$last_opt + 1}] |
||||||
|
|
||||||
|
#puts stderr "first_non_opt: $first_non_opt" |
||||||
|
set opts [lrange $args -1 $first_non_opt-1] |
||||||
|
set paths [lrange $args $first_non_opt end] |
||||||
|
if {![llength $paths]} { |
||||||
|
error "Unable to find file in the supplied arguments: $args. Ensure options are all -opt val pairs and that file name(s) follow" |
||||||
|
} |
||||||
|
#puts stderr "opts: $opts paths: $paths" |
||||||
|
set finalpaths [list] |
||||||
|
foreach p $paths { |
||||||
|
if {$has_winpath && [punk::winpath::illegalname_test $p]} { |
||||||
|
lappend finalpaths [punk::winpath::illegalname_fix $p] |
||||||
|
} else { |
||||||
|
lappend finalpaths $p |
||||||
|
} |
||||||
|
} |
||||||
|
fileutil::cat {*}$opts {*}$finalpaths |
||||||
|
} |
||||||
|
|
||||||
|
#---------------------------------------- |
||||||
|
namespace eval internal { |
||||||
|
proc path_common_prefix_pop {varname} { |
||||||
|
upvar 1 $varname var |
||||||
|
set var [lassign $var head] |
||||||
|
return $head |
||||||
|
} |
||||||
|
} |
||||||
|
proc path_common_prefix {args} { |
||||||
|
set dirs $args |
||||||
|
set parts [file split [internal::path_common_prefix_pop dirs]] |
||||||
|
while {[llength $dirs]} { |
||||||
|
set r {} |
||||||
|
foreach cmp $parts elt [file split [internal::path_common_prefix_pop dirs]] { |
||||||
|
if {$cmp ne $elt} break |
||||||
|
lappend r $cmp |
||||||
|
} |
||||||
|
set parts $r |
||||||
|
} |
||||||
|
if {[llength $parts]} { |
||||||
|
return [file join {*}$parts] |
||||||
|
} else { |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#retains case from first argument only - caseless comparison |
||||||
|
proc path_common_prefix_nocase {args} { |
||||||
|
set dirs $args |
||||||
|
set parts [file split [internal::path_common_prefix_pop dirs]] |
||||||
|
while {[llength $dirs]} { |
||||||
|
set r {} |
||||||
|
foreach cmp $parts elt [file split [internal::path_common_prefix_pop dirs]] { |
||||||
|
if {![string equal -nocase $cmp $elt]} break |
||||||
|
lappend r $cmp |
||||||
|
} |
||||||
|
set parts $r |
||||||
|
} |
||||||
|
if {[llength $parts]} { |
||||||
|
return [file join {*}$parts] |
||||||
|
} else { |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
||||||
|
#---------------------------------------- |
||||||
|
|
||||||
|
#maint warning - also in punkcheck |
||||||
|
proc path_relative {base dst} { |
||||||
|
#see also kettle |
||||||
|
# Modified copy of ::fileutil::relative (tcllib) |
||||||
|
# Adapted to 8.5 ({*}). |
||||||
|
# |
||||||
|
# Taking two _directory_ paths, a base and a destination, computes the path |
||||||
|
# of the destination relative to the base. |
||||||
|
# |
||||||
|
# Arguments: |
||||||
|
# base The path to make the destination relative to. |
||||||
|
# dst The destination path |
||||||
|
# |
||||||
|
# Results: |
||||||
|
# The path of the destination, relative to the base. |
||||||
|
|
||||||
|
# Ensure that the link to directory 'dst' is properly done relative to |
||||||
|
# the directory 'base'. |
||||||
|
|
||||||
|
#review - check volume info on windows.. UNC paths? |
||||||
|
if {[file pathtype $base] ne [file pathtype $dst]} { |
||||||
|
return -code error "Unable to compute relation for paths of different pathtypes: [file pathtype $base] vs. [file pathtype $dst], ($base vs. $dst)" |
||||||
|
} |
||||||
|
|
||||||
|
#avoid normalizing if possible (file normalize *very* expensive on windows) |
||||||
|
set do_normalize 0 |
||||||
|
if {[file pathtype $base] eq "relative"} { |
||||||
|
#if base is relative so is dst |
||||||
|
if {[regexp {[.]{2}} [list $base $dst]]} { |
||||||
|
set do_normalize 1 |
||||||
|
} |
||||||
|
if {[regexp {[.]/} [list $base $dst]]} { |
||||||
|
set do_normalize 1 |
||||||
|
} |
||||||
|
} else { |
||||||
|
set do_normalize 1 |
||||||
|
} |
||||||
|
if {$do_normalize} { |
||||||
|
set base [file normalize $base] |
||||||
|
set dst [file normalize $dst] |
||||||
|
} |
||||||
|
|
||||||
|
set save $dst |
||||||
|
set base [file split $base] |
||||||
|
set dst [file split $dst] |
||||||
|
|
||||||
|
while {[lindex $dst 0] eq [lindex $base 0]} { |
||||||
|
set dst [lrange $dst 1 end] |
||||||
|
set base [lrange $base 1 end] |
||||||
|
if {![llength $dst]} {break} |
||||||
|
} |
||||||
|
|
||||||
|
set dstlen [llength $dst] |
||||||
|
set baselen [llength $base] |
||||||
|
|
||||||
|
if {($dstlen == 0) && ($baselen == 0)} { |
||||||
|
# Cases: |
||||||
|
# (a) base == dst |
||||||
|
|
||||||
|
set dst . |
||||||
|
} else { |
||||||
|
# Cases: |
||||||
|
# (b) base is: base/sub = sub |
||||||
|
# dst is: base = {} |
||||||
|
|
||||||
|
# (c) base is: base = {} |
||||||
|
# dst is: base/sub = sub |
||||||
|
|
||||||
|
while {$baselen > 0} { |
||||||
|
set dst [linsert $dst 0 ..] |
||||||
|
incr baselen -1 |
||||||
|
} |
||||||
|
set dst [file join {*}$dst] |
||||||
|
} |
||||||
|
|
||||||
|
return $dst |
||||||
|
} |
||||||
|
|
||||||
|
proc namespace_import_pattern_to_namespace_noclobber {pattern ns} { |
||||||
|
set source_ns [namespace qualifiers $pattern] |
||||||
|
if {![namespace exists $source_ns]} { |
||||||
|
error "namespace_import_pattern_to_namespace_noclobber error namespace $source_ns not found" |
||||||
|
} |
||||||
|
if {![string match ::* $ns]} { |
||||||
|
set nscaller [uplevel 1 {namespace current}] |
||||||
|
set ns [punk::nsjoin $nscaller $ns] |
||||||
|
} |
||||||
|
set a_export_patterns [namespace eval $source_ns {namespace export}] |
||||||
|
set a_commands [info commands $pattern] |
||||||
|
set a_tails [lmap v $a_commands {namespace tail $v}] |
||||||
|
set a_exported_tails [list] |
||||||
|
foreach pattern $a_export_patterns { |
||||||
|
set matches [lsearch -all -inline $a_tails $pattern] |
||||||
|
foreach m $matches { |
||||||
|
if {$m ni $a_exported_tails} { |
||||||
|
lappend a_exported_tails $m |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
set imported_commands [list] |
||||||
|
foreach e $a_exported_tails { |
||||||
|
set imported [namespace eval $ns [string map [list <func> $e <a> $source_ns] { |
||||||
|
set cmd "" |
||||||
|
if {![catch {namespace import <a>::<func>}]} { |
||||||
|
set cmd <func> |
||||||
|
} |
||||||
|
set cmd |
||||||
|
}]] |
||||||
|
if {[string length $imported]} { |
||||||
|
lappend imported_commands $imported |
||||||
|
} |
||||||
|
} |
||||||
|
return $imported_commands |
||||||
|
} |
||||||
|
|
||||||
|
proc askuser {question} { |
||||||
|
puts stdout $question |
||||||
|
flush stdout |
||||||
|
set stdin_state [fconfigure stdin] |
||||||
|
fconfigure stdin -blocking 1 |
||||||
|
set answer [gets stdin] |
||||||
|
fconfigure stdin -blocking [dict get $stdin_state -blocking] |
||||||
|
return $answer |
||||||
|
} |
||||||
|
|
||||||
|
proc do_in_path {path script} { |
||||||
|
#from ::kettle::path::in |
||||||
|
set here [pwd] |
||||||
|
try { |
||||||
|
cd $path |
||||||
|
uplevel 1 $script |
||||||
|
} finally { |
||||||
|
cd $here |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc foreach-file {path script_pathvariable script} { |
||||||
|
upvar 1 $script_pathvariable thepath |
||||||
|
|
||||||
|
set known {} |
||||||
|
lappend waiting $path |
||||||
|
while {[llength $waiting]} { |
||||||
|
set pending $waiting |
||||||
|
set waiting {} |
||||||
|
set at 0 |
||||||
|
while {$at < [llength $pending]} { |
||||||
|
set current [lindex $pending $at] |
||||||
|
incr at |
||||||
|
|
||||||
|
# Do not follow into parent. |
||||||
|
if {[string match *.. $current]} continue |
||||||
|
|
||||||
|
# Ignore what we have visited already. |
||||||
|
set c [file dirname [file normalize $current/___]] |
||||||
|
if {[dict exists $known $c]} continue |
||||||
|
dict set known $c . |
||||||
|
|
||||||
|
if {[file tail $c] eq ".git"} { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
# Expand directories. |
||||||
|
if {[file isdirectory $c]} { |
||||||
|
lappend waiting {*}[lsort -unique [glob -directory $c * .*]] |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
# Handle files as per the user's will. |
||||||
|
set thepath $current |
||||||
|
switch -exact -- [catch { uplevel 1 $script } result] { |
||||||
|
0 - 4 { |
||||||
|
# ok, continue - nothing |
||||||
|
} |
||||||
|
2 { |
||||||
|
# return, abort, rethrow |
||||||
|
return -code return |
||||||
|
} |
||||||
|
3 { |
||||||
|
# break, abort |
||||||
|
return |
||||||
|
} |
||||||
|
1 - default { |
||||||
|
# error, any thing else - rethrow |
||||||
|
return -code error $result |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
proc is_valid_tm_version {versionpart} { |
||||||
|
#Needs to be suitable for use with Tcl's 'package vcompare' |
||||||
|
if {![catch [list package vcompare $versionpart $versionpart]]} { |
||||||
|
return 1 |
||||||
|
} else { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
#Note that semver only has a small overlap with tcl tm versions. |
||||||
|
#todo - work out what overlap and whether it's even useful |
||||||
|
#see also TIP #439: Semantic Versioning (tcl 9??) |
||||||
|
proc semver {versionstring} { |
||||||
|
set re {^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$} |
||||||
|
} |
||||||
|
#todo - semver conversion/validation for other systems? |
||||||
|
proc magic_tm_version {} { |
||||||
|
set magicbase 999999 ;#deliberately large so given load-preference when testing! |
||||||
|
#we split the literal to avoid the literal appearing here - reduce risk of accidentally converting to a release version |
||||||
|
return ${magicbase}.0a1.0 |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
proc tmpfile {{prefix tmp_}} { |
||||||
|
#note risk of collision if pregenerating a list of tmpfile names |
||||||
|
#we will maintain an icrementing id so the caller doesn't have to bear that in mind |
||||||
|
variable tmpfile_counter |
||||||
|
global tcl_platform |
||||||
|
return .punkutil_$prefix[pid]_[clock microseconds]_[incr tmpfile_counter]_[info hostname]_$tcl_platform(user) |
||||||
|
} |
||||||
|
|
||||||
|
proc tmpdir {} { |
||||||
|
# Taken from tcllib fileutil. |
||||||
|
global tcl_platform env |
||||||
|
|
||||||
|
set attempdirs [list] |
||||||
|
set problems {} |
||||||
|
|
||||||
|
foreach tmp {TEMP TMP TMPDIR} { |
||||||
|
if { [info exists env($tmp)] } { |
||||||
|
lappend attempdirs $env($tmp) |
||||||
|
} else { |
||||||
|
lappend problems "No environment variable $tmp" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
switch $tcl_platform(platform) { |
||||||
|
windows { |
||||||
|
lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP" |
||||||
|
} |
||||||
|
macintosh { |
||||||
|
lappend attempdirs $env(TRASH_FOLDER) ;# a better place? |
||||||
|
} |
||||||
|
default { |
||||||
|
lappend attempdirs \ |
||||||
|
[file join / tmp] \ |
||||||
|
[file join / var tmp] \ |
||||||
|
[file join / usr tmp] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
lappend attempdirs [pwd] |
||||||
|
|
||||||
|
foreach tmp $attempdirs { |
||||||
|
if { [file isdirectory $tmp] && |
||||||
|
[file writable $tmp] } { |
||||||
|
return [file normalize $tmp] |
||||||
|
} elseif { ![file isdirectory $tmp] } { |
||||||
|
lappend problems "Not a directory: $tmp" |
||||||
|
} else { |
||||||
|
lappend problems "Not writable: $tmp" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# Fail if nothing worked. |
||||||
|
return -code error "Unable to determine a proper directory for temporary files\n[join $problems \n]" |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::mix::util [namespace eval punk::mix::util { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,3 @@ |
|||||||
0.1.0 |
0.1.1 |
||||||
#First line must be a semantic version number |
#First line must be a semantic version number |
||||||
#all other lines are ignored. |
#all other lines are ignored. |
||||||
|
@ -0,0 +1,104 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::tdl 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::tdl { |
||||||
|
# https://wiki.tcl-lang.org/page/Config+file+using+slave+interp |
||||||
|
|
||||||
|
variable sample_script { |
||||||
|
server -name bsd1 -os FreeBSD |
||||||
|
server -name p1 -os linux |
||||||
|
server -name trillion -os windows |
||||||
|
|
||||||
|
server -name vmhost1 -os FreeBSD { |
||||||
|
guest -name bsd1 -vmmanager iocage |
||||||
|
guest -name p1 -vmmanager bhyve |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc prettyparse {script} { |
||||||
|
set i [interp create -safe] |
||||||
|
try { |
||||||
|
# $i eval {unset {*}[info vars]} |
||||||
|
# foreach command [$i eval {info commands}] {$i hide $command} |
||||||
|
# $i invokehidden namespace delete {*}[$i invokehidden namespace children] |
||||||
|
$i alias unknown apply {{i tag args} { |
||||||
|
upvar 1 result result |
||||||
|
set e [concat [list tag $tag]\ |
||||||
|
[lrange $args 0 [expr {([llength $args] & ~1) - 1}]]] |
||||||
|
if {[llength $args] % 2} { |
||||||
|
set saved $result |
||||||
|
set result {} |
||||||
|
$i eval [lindex $args end] |
||||||
|
lappend e body $result |
||||||
|
set result $saved |
||||||
|
} |
||||||
|
lappend result $e |
||||||
|
list |
||||||
|
}} $i |
||||||
|
set result {} |
||||||
|
$i eval $script |
||||||
|
return $result |
||||||
|
} finally { |
||||||
|
interp delete $i |
||||||
|
} |
||||||
|
} |
||||||
|
proc prettyprint {data {level 0}} { |
||||||
|
set ind [string repeat " " $level] |
||||||
|
incr level |
||||||
|
set result {} |
||||||
|
foreach e $data { |
||||||
|
set line $ind[concat [list [dict get $e tag]] [dict remove $e tag body]] |
||||||
|
if {[dict exists $e body] && [llength [dict get $e body]]} { |
||||||
|
append line " {\n[prettyprint [dict get $e body] $level]\n$ind}" |
||||||
|
} |
||||||
|
lappend result $line |
||||||
|
} |
||||||
|
join $result \n |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::tdl [namespace eval punk::tdl { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -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) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::temp2 0.1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::temp2 { |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::temp2 [namespace eval punk::temp2 { |
||||||
|
variable version |
||||||
|
set version 0.1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,233 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::unixywindows 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
#for illegalname_test |
||||||
|
package require punk::winpath |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::unixywindows { |
||||||
|
#'cached' name to make obvious it could be out of date - and to distinguish from unixyroot arg |
||||||
|
variable cachedunixyroot "" |
||||||
|
|
||||||
|
|
||||||
|
#----------------- |
||||||
|
#e.g something like c:/Users/geek/scoop/apps/msys2/current c:/msys2 |
||||||
|
proc get_unixyroot {} { |
||||||
|
variable cachedunixyroot |
||||||
|
if {![string length $cachedunixyroot]} { |
||||||
|
if {![catch { |
||||||
|
set result [exec cygpath -m /] ;# -m gives result with forward-slashes - which is ok for windows paths in a Tcl context. |
||||||
|
set cachedunixyroot [punk::objclone $result] |
||||||
|
file pathtype $cachedunixyroot; #this call causes the int-rep to be path |
||||||
|
set ::punk::last_run_display [list] ;#hack - review shouldn't really be necessary.. but because we call winpath from ./ - the repl looks for last_run_display |
||||||
|
} errM]} { |
||||||
|
|
||||||
|
} else { |
||||||
|
puts stderr "Warning: Failed to determine base for unix-like paths - using default of c:/msys2" |
||||||
|
file pathtype [set cachedunixyroot [punk::objclone "c:/msys2"]] |
||||||
|
} |
||||||
|
} |
||||||
|
#will have been shimmered from string to 'path' internal rep by 'file pathtype' call |
||||||
|
|
||||||
|
#let's return a different copy as it's so easy to lose path-rep |
||||||
|
set copy [punk::objclone $cachedunixyroot] |
||||||
|
return $copy |
||||||
|
} |
||||||
|
proc refresh_unixyroot {} { |
||||||
|
variable cachedunixyroot |
||||||
|
set result [exec cygpath -m /] ;# -m gives result with forward-slashes - which is ok for windows paths in a Tcl context. |
||||||
|
set cachedunixyroot [punk::objclone $result] |
||||||
|
file pathtype $cachedunixyroot; #this call causes the int-rep to be path |
||||||
|
|
||||||
|
set copy [punk::objclone $cachedunixyroot] |
||||||
|
return $copy |
||||||
|
} |
||||||
|
proc set_unixyroot {windows_path} { |
||||||
|
variable cachedunixyroot |
||||||
|
file pathtype $windows_path |
||||||
|
set cachedunixyroot [punk::objclone $windows_path] |
||||||
|
#return the original - but probably int-rep will have shimmered to path even if started out as string |
||||||
|
#- that's probably what we want. Caller can use as string or path without affecting cachedunixyroot |
||||||
|
return $windows_path |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc windir {path} { |
||||||
|
if {$path eq "~"} { |
||||||
|
#as the tilde hasn't been normalized.. we can't assume we're running on the actual platform |
||||||
|
return ~/.. |
||||||
|
} |
||||||
|
return [file dirname [winpath $path]] |
||||||
|
} |
||||||
|
|
||||||
|
#REVIEW high-coupling |
||||||
|
proc cdwin {path} { |
||||||
|
set path [winpath $path] |
||||||
|
if {$::repl::running} { |
||||||
|
repl::term::set_console_title $path |
||||||
|
} |
||||||
|
cd $path |
||||||
|
} |
||||||
|
proc cdwindir {path} { |
||||||
|
set path [winpath $path] |
||||||
|
if {$::repl::running} { |
||||||
|
repl::term::set_console_title $path |
||||||
|
} |
||||||
|
cd [file dirname $path] |
||||||
|
} |
||||||
|
|
||||||
|
#NOTE - this is an expensive operation - avoid where possible. |
||||||
|
#review - is this intended to be useful/callable on non-windows platforms? |
||||||
|
#it should in theory be useable from another platform that wants to create a path for use on windows. |
||||||
|
#In this case - we shouldn't examine what volumes exist (assume A: .. Z: are valid) |
||||||
|
#review zipfs:// other uri schemes? |
||||||
|
proc towinpath {unixypath {unixyroot ""}} { |
||||||
|
#NOTE: tcl file exists gives different answers on windows for paths like /c depending on cwd (presumably based on file pathtype of volumerelative) |
||||||
|
#(Tcl is also somewhat broken as at 2023 as far as volume relative paths - process can get out of sync with tcl if cd to a vol relative path is used) |
||||||
|
#This is add odds with attempting to navigate on a windows system which has cygwin, wsl etc... It also makes it difficult for functions intended to operate independent of CWD. |
||||||
|
#e.g there is potential confusion when there is a c folder on c: drive (c:/c) |
||||||
|
#I will attempt to provide a coherent operation for winpath ./ ../ etc , but it may disallow for example; change to /something or /x where these don't match a driveletter or /mnt |
||||||
|
#whereas tcl may allow cd to /something if a something folder happens to exist on the current volume based on cwd. |
||||||
|
#I think it's preferable to require an explicit driveletter /x or /mnt when using unix-like paths on windows - but practical considerations may prove me wrong.. |
||||||
|
#It's possible that this function should also ignore the current set of driveletters - and operate completely independent of whether a path actually exists |
||||||
|
#This makes it hard to use things like 'file normalize' - which also looks at things like current volume. |
||||||
|
# |
||||||
|
#Note for example the results of 'which' grep on windows can produce a path like /c/Users/somewhere/bin/grep |
||||||
|
#which tcl's file normalize may change to C:/c/Users or X:/c/Users - based on current volumen. Given that C:/c might exist - this can be problematic in a couple of ways. |
||||||
|
#The mixing of unix-like and windows commands on the same machine is a large part of the problem.. but this mix is now common |
||||||
|
# |
||||||
|
#convert /c/etc to C:/etc |
||||||
|
set re_slash_x_slash {^/([[:alpha:]]){1}/.*} |
||||||
|
set re_slash_else {^/([[:alpha:]]*)(.*)} |
||||||
|
set volumes [file volumes] |
||||||
|
#exclude things like //zipfs:/ ?? |
||||||
|
set driveletters [list] |
||||||
|
foreach v $volumes { |
||||||
|
if {[regexp {^([[:alpha:]]){1}:/$} $v _ letter]} { |
||||||
|
lappend driveletters $letter |
||||||
|
} |
||||||
|
} |
||||||
|
#puts stderr "->$driveletters" |
||||||
|
|
||||||
|
set path [punk::objclone $unixypath] ;#take another copy that we can deliberatley shimmer to path and know is separate to the supplied argument |
||||||
|
set supplied_pathtype [file pathtype $path] ;#we don't care about the pathtype so much as the act of making this call shimmers to a path internal-rep |
||||||
|
|
||||||
|
#copy of var that we can treat as a string without affecting path rep |
||||||
|
#Note that some but not all read operations will lose path rep e.g 'string length $x' will lose any path-rep $x had, (but 'string equal $x something' won't) |
||||||
|
#Todo - make int-rep tests to check stability of these behaviours across Tcl versions! |
||||||
|
set strcopy_path [punk::objclone $path] |
||||||
|
|
||||||
|
set str_newpath "" |
||||||
|
|
||||||
|
set have_pathobj 0 |
||||||
|
|
||||||
|
if {[regexp $re_slash_x_slash $strcopy_path _ letter]} { |
||||||
|
#upper case appears to be windows canonical form |
||||||
|
set str_newpath [string toupper $letter]:/[string range $strcopy_path 3 end] |
||||||
|
} elseif {[regexp {^/mnt/([[:alpha:]]){1}/.*} [string tolower $strcopy_path] _ letter]} { |
||||||
|
set str_newpath [string toupper $letter]:/[string range $strcopy_path 7 end] |
||||||
|
} elseif {[regexp {^/mnt/([[:alpha:]]){1}$} [string tolower $strcopy_path] _ letter]} { |
||||||
|
set str_newpath [string toupper $letter]:/ |
||||||
|
} elseif {[regexp $re_slash_else $strcopy_path _ firstpart remainder]} { |
||||||
|
#could be for example /c or /something/users |
||||||
|
if {[string length $firstpart] == 1} { |
||||||
|
set letter $firstpart |
||||||
|
set str_newpath [string toupper $letter]:/ |
||||||
|
} else { |
||||||
|
#according to regex we have a single leading slash |
||||||
|
set str_tail [string range $strcopy_path 1 end] |
||||||
|
if {$unixyroot eq ""} { |
||||||
|
set unixyroot [get_unixyroot] |
||||||
|
} else { |
||||||
|
file pathtype $unixyroot; #side-effect generates int-rep of type path ) |
||||||
|
} |
||||||
|
set pathobj [file join $unixyroot $str_tail] |
||||||
|
file pathtype $pathobj |
||||||
|
set have_pathobj 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {!$have_pathobj} { |
||||||
|
if {$str_newpath eq ""} { |
||||||
|
#dunno - pass through |
||||||
|
set pathobj $path |
||||||
|
} else { |
||||||
|
set pathobj [punk::objclone $str_newpath] |
||||||
|
file pathtype $pathobj |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#puts stderr "=> $path" |
||||||
|
#things like 'which' seem to return a path minus the .exe - so we'll just test the containing folder |
||||||
|
# |
||||||
|
#By now file normalize shouldn't do too many shannanigans related to cwd.. |
||||||
|
#We want it to look at cwd for relative paths.. |
||||||
|
#but we don't consider things like /c/Users to be relative even on windows where it would normally mean a volume-relative path e.g c:/c/Users if cwd happens to be somewhere on C: at the time. |
||||||
|
#if {![file exists [file dirname $path]]} { |
||||||
|
# set path [file normalize $path] |
||||||
|
# #may still not exist.. that's ok. |
||||||
|
#} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#file normalize may change backslashes to forward slashes.. including things like the special \\?\ prefix which is intended to stop windows api from parsing a name |
||||||
|
#2023 - this is ok as //?/ also seems to work.. but it is unclear if that is because Tcl is re-converting to backslashes |
||||||
|
if {[punk::winpath::illegalname_test $pathobj]} { |
||||||
|
set pathobj [punk::winpath::illegalname_fix $pathobj] |
||||||
|
} |
||||||
|
|
||||||
|
return $pathobj |
||||||
|
} |
||||||
|
|
||||||
|
#---------------------------------------------- |
||||||
|
#leave the unixywindowws related aliases available on all platforms |
||||||
|
#interp alias {} cdwin {} punk::unixywindows::cdwin |
||||||
|
#interp alias {} cdwindir {} punk::unixywindoes::cdwindir |
||||||
|
#interp alias {} towinpath {} punk::unixywindows::towinpath |
||||||
|
#interp alias {} windir {} punk::unixywindows::windir |
||||||
|
#---------------------------------------------- |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::unixywindows [namespace eval punk::unixywindows { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,164 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punkcheck::cli 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
package require punk::mix::util |
||||||
|
|
||||||
|
namespace eval punkcheck::cli { |
||||||
|
namespace ensemble create |
||||||
|
#package require punk::overlay |
||||||
|
#punk::overlay::import_commandset debug. ::punk:mix::commandset::debug |
||||||
|
|
||||||
|
proc help {args} { |
||||||
|
set basehelp [punk::mix::base help {*}$args] |
||||||
|
return $basehelp |
||||||
|
} |
||||||
|
|
||||||
|
proc paths {{path {}}} { |
||||||
|
if {$path eq {}} { set path [pwd] } |
||||||
|
set search_from $path |
||||||
|
set bottom_to_top [list] |
||||||
|
while {[string length [set pcheck_file [punkcheck::cli::lib::find_nearest_file $search_from]]]} { |
||||||
|
set pcheck_folder [file dirname $pcheck_file] |
||||||
|
lappend bottom_to_top $pcheck_file |
||||||
|
set search_from [file dirname $pcheck_folder] |
||||||
|
} |
||||||
|
return $bottom_to_top |
||||||
|
} |
||||||
|
proc status {{path {}}} { |
||||||
|
if {$path eq {}} { set path [pwd] } |
||||||
|
set fullpath [file normalize $path] |
||||||
|
set ftype [file type $fullpath] |
||||||
|
set files [list] |
||||||
|
if {$ftype eq "file"} { |
||||||
|
set container [file dirname $fullpath] |
||||||
|
lappend files $fullpath |
||||||
|
} else { |
||||||
|
set container $fullpath |
||||||
|
set files [glob -nocomplain -dir $fullpath -type f *] |
||||||
|
} |
||||||
|
set punkcheck_files [paths $container] |
||||||
|
set repodict [punk::repo::find_repo $container] |
||||||
|
|
||||||
|
if {![llength $punkcheck_files]} { |
||||||
|
puts stderr "No .punkcheck files found at or above this folder" |
||||||
|
} |
||||||
|
|
||||||
|
set table "" |
||||||
|
foreach p $punkcheck_files { |
||||||
|
set basedir [file dirname $p] |
||||||
|
set recordlist [punkcheck::load_records_from_file $p] |
||||||
|
set tgt_dict [punkcheck::recordlist::records_as_target_dict $recordlist] |
||||||
|
foreach f $files { |
||||||
|
set relpath [punkcheck::lib::path_relative $basedir $f] |
||||||
|
if {[dict exists $tgt_dict $relpath]} { |
||||||
|
if {[llength $files] == 1} { |
||||||
|
set filerec [dict get $tgt_dict $relpath] |
||||||
|
set records [punkcheck::dict_getwithdefault $filerec body [list]] |
||||||
|
if {![llength $records]} { |
||||||
|
set pcheck "(has file record but no installation entries)" |
||||||
|
} else { |
||||||
|
set pcheck \n |
||||||
|
foreach irec $records { |
||||||
|
append pcheck [punk::tdl::prettyprint [list $irec] 1] \n |
||||||
|
#append pcheck " $irec" \n |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
set pcheck "(has record)" |
||||||
|
} |
||||||
|
} else { |
||||||
|
set pcheck "" |
||||||
|
} |
||||||
|
append table "$f $pcheck" \n |
||||||
|
} |
||||||
|
} |
||||||
|
return $table |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punkcheck::cli::lib { |
||||||
|
namespace path ::punk::mix::util ;#askuser, do_in_path, foreach-file etc |
||||||
|
|
||||||
|
proc find_nearest_file {{path {}}} { |
||||||
|
if {$path eq {}} { set path [pwd] } |
||||||
|
set folder [lib::scanup $path lib::is_punkchecked_folder] |
||||||
|
if {$folder eq ""} { |
||||||
|
return "" |
||||||
|
} else { |
||||||
|
return [file join $folder .punkcheck] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc is_punkchecked_folder {{path {}}} { |
||||||
|
if {$path eq {}} { set path [pwd] } |
||||||
|
foreach control { |
||||||
|
.punkcheck |
||||||
|
} { |
||||||
|
set control [file join $path $control] |
||||||
|
if {[file isfile $control]} {return 1} |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
proc scanup {path cmd} { |
||||||
|
if {$path eq {}} { set path [pwd] } |
||||||
|
#based on kettle::path::scanup |
||||||
|
if {[file pathtype $path] eq "relative"} { |
||||||
|
set path [file normalize $path] |
||||||
|
} |
||||||
|
while {1} { |
||||||
|
# Found the proper directory, per the predicate. |
||||||
|
if {[{*}$cmd $path]} { return $path } |
||||||
|
|
||||||
|
# Not found, walk to parent |
||||||
|
set new [file dirname $path] |
||||||
|
|
||||||
|
# Stop when reaching the root. |
||||||
|
if {$new eq $path} { return {} } |
||||||
|
if {$new eq {}} { return {} } |
||||||
|
|
||||||
|
# Ok, truly walk up. |
||||||
|
set path $new |
||||||
|
} |
||||||
|
return {} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punkcheck::cli { |
||||||
|
proc _cli {args} { |
||||||
|
#don't use tailcall - base uses info level to determine caller |
||||||
|
::punk::mix::base::_cli {*}$args |
||||||
|
} |
||||||
|
variable default_command status |
||||||
|
package require punk::mix::base |
||||||
|
package require punk::overlay |
||||||
|
punk::overlay::custom_from_base [namespace current] ::punk::mix::base |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punkcheck::cli [namespace eval punkcheck::cli { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,107 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
||||||
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# (C) 2023 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application textblock 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
package require punk |
||||||
|
package require patternpunk |
||||||
|
package require overtype |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval textblock { |
||||||
|
|
||||||
|
proc width {block} { |
||||||
|
if {![llength $block]} { |
||||||
|
return [string length [stripcodes $block]] |
||||||
|
} |
||||||
|
tcl::mathfunc::max {*}[lmap v [linelist $block] {string length [stripcodes $v]}] |
||||||
|
} |
||||||
|
|
||||||
|
pipealias ::textblock::padleft .= {list $input [string repeat " " $indent]} |/0,padding/1> linelist |> .= {lmap v $data {val "$padding$v"}} |> list_as_lines <input/0,indent/1| |
||||||
|
pipealias ::textblock::padright .= {list $input [string repeat " " $colsize]} |/0,padding/1> linelist |> .= {lmap v $data {val "$v$padding"}} |> list_as_lines <input/0,colsize/1| |
||||||
|
|
||||||
|
pipealias ::textblock::join_width .= {list $lhs [string repeat " " $w1] $rhs [string repeat " " $w2]} {| |
||||||
|
/2,col1/1,col2/3 |
||||||
|
>} linelist {| |
||||||
|
data2 |
||||||
|
>} .=lhs> linelist {| |
||||||
|
>} .= {lmap v $data w $data2 {val "[overtype::left $col1 $v][overtype::left $col2 $w]"}} {| |
||||||
|
>} list_as_lines <lhs/0,w1/1,rhs/2,w2/3| |
||||||
|
|
||||||
|
pipealias ::textblock::join .= {list $lhs [string repeat " " [width $lhs]] $rhs [string repeat " " [width $rhs]]} {| |
||||||
|
/2,col1/1,col2/3 |
||||||
|
>} linelist {| |
||||||
|
data2 |
||||||
|
>} .=lhs> linelist {| |
||||||
|
>} .= {lmap v $data w $data2 {val "[overtype::left $col1 $v][overtype::left $col2 $w]"}} {| |
||||||
|
>} list_as_lines <lhs/0,rhs/1| |
||||||
|
|
||||||
|
pipealias ::textblock::join_right .= {list $lhs [string repeat " " [width $lhs]] $rhs [string repeat " " [width $rhs]]} {| |
||||||
|
/2,col1/1,col2/3 |
||||||
|
>} linelist {| |
||||||
|
data2 |
||||||
|
>} .=lhs> linelist {| |
||||||
|
>} .= {lmap v $data w $data2 {val "[overtype::right $col1 $v][overtype::right $col2 $w]"}} {| |
||||||
|
>} list_as_lines <lhs/0,rhs/1| |
||||||
|
|
||||||
|
proc example {{text "test\netc\nmore text"}} { |
||||||
|
package require patternpunk |
||||||
|
.= textblock::join [list 1 2 3 4 5 6 7] [>punk . lhs] |> .=>1 textblock::join " " |> .=>1 textblock::join $text |> .=>1 textblock::join [>punk . rhs] |> .=>1 textblock::join [lrepeat 7 " | "] |
||||||
|
} |
||||||
|
|
||||||
|
#maintenance warning - also in 'shellfilter' pkg |
||||||
|
#strip ansi codes from text - basic! assumes we don't get data split in the middle of an ansi-code ie best used with line-buffering |
||||||
|
proc stripcodes {text} { |
||||||
|
if {[set posn [string first "\033\[" $text]] >= 0} { |
||||||
|
set mnext [string first m [string range $text $posn end]] |
||||||
|
if {$mnext >= 0} { |
||||||
|
set mpos [expr {$posn + $mnext}] |
||||||
|
set stripped1 [string range $text 0 $posn-1][string range $text $mpos+1 end] |
||||||
|
#return [stripcodes $stripped1] ;#recurse to get any others |
||||||
|
tailcall ::textblock::stripcodes $stripped1 |
||||||
|
} else { |
||||||
|
#partial or not actually an ansi code.. pass it all through |
||||||
|
return $text |
||||||
|
} |
||||||
|
} else { |
||||||
|
return $text |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide textblock [namespace eval textblock { |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
@ -0,0 +1,3 @@ |
|||||||
|
0.1.0 |
||||||
|
#First line must be a semantic version number |
||||||
|
#all other lines are ignored. |
@ -0,0 +1,112 @@ |
|||||||
|
: "[proc : args {}]" ;# *tcl shellbat - call with sh,bash,tclsh on any platform, or with cmd on windows. |
||||||
|
: <<'HIDE_FROM_BASH_AND_SH' |
||||||
|
: ;# leading colon hides from .bat, trailing slash hides next line from tcl \ |
||||||
|
@call tclsh "%~dp0%~n0.bat" %* |
||||||
|
: ;#\ |
||||||
|
@set taskexitcode=%errorlevel% & goto :exit |
||||||
|
# -*- tcl -*- |
||||||
|
# ################################################################################################# |
||||||
|
# This is a tcl shellbat file |
||||||
|
# It is tuned to run when called as a batch file, a tcl script, an sh script or a bash script, |
||||||
|
# so the specific layout and characters used are quite sensitive to change. |
||||||
|
# It can be called on unix or windows platforms with or without the interpreter being specified on the commandline. |
||||||
|
# e.g ./filename.sh.bat in sh or bash or powershell |
||||||
|
# e.g filename.sh or filename.sh.bat at windows command prompt |
||||||
|
# e.g tclsh filename.sh.bat | sh filename.sh.bat | bash filename.sh.bat |
||||||
|
# In all cases an arbitrary number of arguments are accepted |
||||||
|
# To avoid the initial commandline on stdout when calling as a batch file on windows, use: |
||||||
|
# cmd /Q /c filename.sh.bat |
||||||
|
# (because we cannot use @if to silence it, as this isn't understood by tcl,sh or bash) |
||||||
|
# ################################################################################################# |
||||||
|
#fconfigure stdout -translation crlf |
||||||
|
# --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload |
||||||
|
#puts "script : [info script]" |
||||||
|
#puts "argcount : $::argc" |
||||||
|
#puts "argvalues: $::argv" |
||||||
|
|
||||||
|
|
||||||
|
#<tcl-payload> |
||||||
|
#<tcl-payload/> |
||||||
|
|
||||||
|
# --- --- --- --- --- --- --- --- --- --- --- --- --- |
||||||
|
# only exit if needed. see exitcode notes at bottom of file and exit there for consistency across invocation methods |
||||||
|
# --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload |
||||||
|
#-- |
||||||
|
#-- bash/sh code follows. |
||||||
|
#-- protect from tcl using line continuation char on the previous comment for each line, like so: \ |
||||||
|
printf "etc" |
||||||
|
#-- or alternatively place sh/bash script within the false==false block |
||||||
|
#-- whilst being careful to balance braces {} |
||||||
|
#-- For more complex needs you should call out to external scripts |
||||||
|
#-- |
||||||
|
#-- END marker for hide_from_bash_and_sh\ |
||||||
|
HIDE_FROM_BASH_AND_SH |
||||||
|
|
||||||
|
#--------------------------------------------------------- |
||||||
|
#-- This if statement hides(mostly) a sh/bash code block from Tcl |
||||||
|
if false==false # else { |
||||||
|
then |
||||||
|
: |
||||||
|
#--------------------------------------------------------- |
||||||
|
#-- leave as is if all that's required is launching the Tcl payload" |
||||||
|
#-- |
||||||
|
#-- Note that sh/bash script isn't called when running a .bat from cmd.exe on windows by default |
||||||
|
#-- adjust line 4: @call tclsh ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate |
||||||
|
#-- if sh/bash scripting needs to run on windows too. |
||||||
|
#-- |
||||||
|
#printf "start of bash or sh code" |
||||||
|
|
||||||
|
#<shell-payload-pre-tcl> |
||||||
|
#</shell-payload-pre-tcl> |
||||||
|
|
||||||
|
|
||||||
|
#-- sh/bash launches Tcl here instead of shebang line at top |
||||||
|
#<shell-launch-tcl> |
||||||
|
#-- use exec to use exitcode (if any) directly from the tcl script |
||||||
|
exec /usr/bin/env tclsh "$0" "$@" |
||||||
|
#</shell-launch-tcl> |
||||||
|
|
||||||
|
#-- alternative - if sh/bash script required to run after the tcl call. |
||||||
|
#/usr/bin/env tclsh "$0" "$@" |
||||||
|
#tcl_exitcode=$? |
||||||
|
#echo "tcl_exitcode: ${tcl_exitcode}" |
||||||
|
|
||||||
|
#<shell-payload-post-tcl> |
||||||
|
#</shell-payload-post-tcl> |
||||||
|
|
||||||
|
#-- override exitcode example |
||||||
|
#exit 66 |
||||||
|
|
||||||
|
#printf "No need for trailing slashes for sh/bash code here\n" |
||||||
|
#--------------------------------------------------------- |
||||||
|
fi |
||||||
|
# closing brace for Tcl } |
||||||
|
#--------------------------------------------------------- |
||||||
|
|
||||||
|
#-- tcl and shell script now both active |
||||||
|
|
||||||
|
#-- comment for line sample 1 with trailing continuation slash \ |
||||||
|
#printf "tcl-invisible sh/bash line sample 1 \n" |
||||||
|
|
||||||
|
#-- comment for line sample 2 with trailing continuation slash \ |
||||||
|
#printf "tcl-invisible sh/bash line sample 2 \n" |
||||||
|
|
||||||
|
|
||||||
|
#-- Consistent exitcode from sh,bash,tclsh or cmd |
||||||
|
#-- Call exit in tcl (or sh/bash) code only if explicitly required, otherwise leave this commented out. |
||||||
|
#-- (script might be more widely useable without explicit exit. e.g in tcl: set ::argc 1; set ::argv "val"; source filename.sh.bat ) |
||||||
|
#-- exit line unprotected by trailing slash will work for tcl and/or sh/bash |
||||||
|
#exit 0 |
||||||
|
#exit 42 |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#-- make sure sh/bash/tcl all skip over .bat style exit \ |
||||||
|
: <<'shell_end' |
||||||
|
#-- .bat exit with exitcode from tcl process \ |
||||||
|
:exit |
||||||
|
: ;# \ |
||||||
|
@exit /B %taskexitcode% |
||||||
|
# .bat has exited \ |
||||||
|
shell_end |
||||||
|
|
Loading…
Reference in new issue