Browse Source

reorg repl, implement punk mix system with project and module templates, change make.tcl layout and make generic

master
Julian Noble 1 year ago
parent
commit
d4191188c2
  1. 213
      src/make.tcl
  2. 43
      src/modules/overtype-1.4.tm
  3. 3712
      src/modules/punk-0.1.tm
  4. 2
      src/modules/punk/config-0.1.tm
  5. 376
      src/modules/punk/lib-0.1.tm
  6. 631
      src/modules/punk/mix-0.2.tm
  7. 216
      src/modules/punk/mix/base-0.1.tm
  8. 13
      src/modules/punk/mix/templates/layouts/project/.fossil-custom/mainmenu
  9. 3
      src/modules/punk/mix/templates/layouts/project/.fossil-settings/empty-dirs
  10. 21
      src/modules/punk/mix/templates/layouts/project/.fossil-settings/ignore-glob
  11. 38
      src/modules/punk/mix/templates/layouts/project/.gitignore
  12. 14
      src/modules/punk/mix/templates/layouts/project/src/README.md
  13. 195
      src/modules/punk/mix/templates/layouts/project/src/make.tcl
  14. 3
      src/modules/punk/mix/templates/module/module_buildversion.txt
  15. 0
      src/modules/punk/mix/templates/module/module_clitemplate-0.0.1.tm
  16. 0
      src/modules/punk/mix/templates/module/module_description.txt
  17. 51
      src/modules/punk/mix/templates/module/module_template-0.0.1.tm
  18. 12
      src/modules/punk/mix_extension-0.1.tm
  19. 62
      src/modules/punk/overlay-0.1.tm
  20. 1317
      src/modules/punk/repl-0.1.tm
  21. 2
      src/modules/punkapp-0.1.tm
  22. 10
      src/modules/shellfilter-0.1.8.tm
  23. BIN
      src/modules/tarjar-2.3.tm
  24. 1199
      src/punk86.vfs/lib/app-punk/repl.tcl
  25. 24
      src/punk86.vfs/lib/app-shellspy/shellspy.tcl

213
src/make.tcl

@ -1,4 +1,11 @@
# tcl
#
#make any tclkits and modules in src and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder.
#It is assumed the src folder has been placed somewhere where appropriate
#(e.g not in /usr or c:/ - unless you intend it to directly make and place folders and files in those locations)
package require punk::mix
if {[lsearch $::argv -k] >= 0} {
set forcekill 1
@ -6,117 +13,126 @@ if {[lsearch $::argv -k] >= 0} {
set forcekill 0
}
puts stdout "::argv $::argv"
set dirname [file normalize [file dirname [info script]]]
set sourcefolder [file normalize [file dirname [info script]]]
# ----------------------------------------
proc copy_modules_in_dir {srcdir basedir {subdirlist {}}} {
set module_list [list]
set src_modules [glob -nocomplain -dir $srcdir -type f -tail *.tm]
if {![file exists $basedir]} {
error "copy_modules_in_dir basedir:'$basedir' doesn't exist"
}
if {[llength $subdirlist] == 0} {
set target_module_dir $basedir
} else {
set target_module_dir $basedir/[file join {*}$subdirlist]
}
foreach m $src_modules {
puts stdout "copying module $srcdir/$m to $target_module_dir"
file copy -force $srcdir/$m $target_module_dir
lappend module_list $srcdir/$m
}
set subdirs [glob -nocomplain -dir $srcdir -type d -tail *]
#puts stderr "subdirs: $subdirs"
foreach d $subdirs {
if {[string match "#*" $d] || ($d eq "_aside")} {
continue
}
if {![file exists $target_module_dir/$d]} {
file mkdir $target_module_dir/$d
}
lappend module_list {*}[copy_modules_in_dir $srcdir/$d $basedir [list {*}$subdirlist $d]]
}
return $module_list
}
set target_modules_base [file dirname $dirname]/modules
set target_modules_base [file dirname $sourcefolder]/modules
file mkdir $target_modules_base
#external modules first
set copied [copy_modules_in_dir $dirname/deps $target_modules_base]
#external modules first - and any supporting files - no 'building' required
set copied [punk::mix::cli::lib::copy_files_from_source_to_base $sourcefolder/deps $target_modules_base -force 1]
puts stderr "Copied [llength $copied] dependencies"
set src_module_dir $dirname/modules
#modules belonging to this package/app
set copied [copy_modules_in_dir $src_module_dir $target_modules_base]
set src_module_dir $sourcefolder/modules
#modules and associated files belonging to this package/app
set copied [punk::mix::cli::lib::build_modules_from_source_to_base $src_module_dir $target_modules_base -glob *.tm] ;#will only accept a glob ending in .tm
puts stderr "Copied [llength $copied] app modules"
#set src_modules [glob -nocomplain -dir $src_module_dir -type f -tail *.tm]
#foreach m $src_modules {
# puts stdout "copying module $src_module_dir/$m to $target_module_dir"
# file copy -force $src_module_dir/$m $target_module_dir
#}
set copied [punk::mix::cli::lib::copy_nonmodules_from_source_to_base $src_module_dir $target_modules_base -force 1]
# ----------------------------------------
#
if {![file exists $dirname/punk86.vfs]} {
puts stderr "missing $dirname/punk86.vfs"
set vfs_folders [glob -nocomplain -dir $sourcefolder -types d -tail *.vfs]
if {![llength $vfs_folders]} {
puts stdout "No .vfs folders found at '$sourcefolder' - no kits to build"
puts stdout " -done- "
exit 0
}
file mkdir $sourcefolder/_build
if {[catch {exec sdx help} errM]} {
puts stderr "FAILED to find usable sdx command - check that sdx executable is on path"
puts stderr "err: $errM"
exit 1
}
if {[file exists $dirname/punk86]} {
puts stderr "deleting existing $dirname/punk86"
file delete $dirname/punk86
#find runtime - only supports one for now.. REVIEW
set rtfolder $sourcefolder/runtime
set runtimes [glob -nocomplain -dir $rtfolder -types {f x} -tail *]
if {![llength $runtimes]} {
puts stderr "No executable runtimes found in $rtfolder - unable to build any .vfs folders into executables."
exit 2
}
if {[llength $runtimes] > 1} {
puts stderr "Found multiple runtimes in $rtfolder ($runtimes) - unable to proceed - currently limited to one."
exit 3
}
puts stdout "building with sdx.."
if {[catch {
exec sdx wrap punk86 -runtime tclkit86bi.exe -verbose
set runtimefile [lindex $runtimes 0]
puts stdout "Found [llength $vfs_folders] .vfs folders - building executable for each..."
foreach vfs $vfs_folders {
set vfsname [file rootname $vfs]
puts stdout " Processing vfs $sourcefolder/$vfs"
puts stdout " ------------------------------------"
if {[file exists $sourcefolder/_build/$vfsname]} {
puts stderr "deleting existing $sourcefolder/_build/$vfsname"
file delete $sourcefolder/_build/$vfsname
}
puts stdout "building $vfsname with sdx.."
if {[catch {
exec sdx wrap _build/$vfsname -runtime runtime/$runtimefile -verbose
} result]} {
puts stderr "sdx wrap punk86 -runtime tclkit86bi.exe -verbose failed with msg: $result"
} else {
puts stderr "sdx wrap _build/$vfsname -runtime runtime/$runtimefile -verbose failed with msg: $result"
} else {
puts stdout "ok - finished sdx"
set separator [string repeat = 40]
puts stdout $separator
puts stdout $result
puts stdout $separator
}
}
if {![file exists $dirname/punk86]} {
puts stderr "|err> build didn't seem to produce output at $dirname/punk86"
if {![file exists $sourcefolder/_build/$vfsname]} {
puts stderr "|err> build didn't seem to produce output at $sourcefolder/_build/$vfsname"
exit 2
}
}
if {$::tcl_platform(platform) eq "windows"} {
set pscmd "tasklist"
} else {
set pscmd "ps"
}
if {![catch {
exec tasklist | grep punk86
if {![catch {
exec $pscmd | grep $vfsname
} still_running]} {
puts stdout "found punk86 instances still running\n"
puts stdout "found $vfsname instances still running\n"
set count_killed 0
foreach ln [split $still_running \n] {
puts stdout " $ln"
if {$::tcl_platform(platform) eq "windows"} {
set pid [lindex $ln 1]
if {$forcekill} {
set killcmd [list taskkill /F /PID $pid]
} else {
set killcmd [list taskkill /PID $pid]
}
} else {
set pid [lindex $ln 0]
#review!
if {$forcekill} {
set killcmd [list kill -9 $pid]
} else {
set killcmd [list kill $pid]
}
}
puts stdout " pid: $pid (attempting to kill now using '$killcmd')"
if {[catch {
exec {*}$killcmd
} errMsg]} {
puts stderr "taskkill /PID $pid returned an error:"
puts stderr "$killcmd returned an error:"
puts stderr $errMsg
puts stderr "(try '[info script] -k' option to force kill)"
exit 4
} else {
puts stderr "taskkill /PID $pid ran without error"
puts stderr "$killcmd ran without error"
incr count_killed
}
}
@ -124,51 +140,56 @@ if {![catch {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
}
} else {
puts stderr "Ok.. no running punk processes found"
}
} else {
puts stderr "Ok.. no running '$vfsname' processes found"
}
if {$::tcl_platform(platform) eq "windows"} {
set targetexe ${vfsname}.exe
} else {
set targetexe $vfsname
}
if {[file exists $dirname/punk86.exe]} {
puts stderr "deleting existing $dirname/punk86.exe"
if {[file exists $sourcefolder/_build/$targetexe]} {
puts stderr "deleting existing $sourcefolder/_build/$targetexe"
if {[catch {
file delete $dirname/punk86.exe
file delete $sourcefolder/_build/$targetexe
} msg]} {
puts stderr "Failed to delete $dirname/punk86.exe"
exit 3
puts stderr "Failed to delete $sourcefolder/_build/$targetexe"
exit 4
}
}
}
#is this test necessary?
if {[file exists $dirname/punk86.exe]} {
puts stderr "deletion of $dirname/punk86.exe failed - locked?"
exit 3
}
if {$::tcl_platform(platform) eq "windows"} {
file rename $sourcefolder/_build/$vfsname $sourcefolder/_build/${vfsname}.exe
}
file rename $dirname/punk86 $dirname/punk86.exe
after 200
set deployment_folder [file dirname $dirname]
after 200
set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder
if {[file exists $deployment_folder/punk86.exe]} {
puts stderr "deleting existing deployed at $deployment_folder/punk86.exe"
if {[file exists $deployment_folder/$targetexe]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetexe"
if {[catch {
file delete $deployment_folder/punk86.exe
file delete $deployment_folder/$targetexe
} errMsg]} {
puts stderr "deletion of deployed version at $deployment_folder/punk86.exe failed: $errMsg"
exit 4
puts stderr "deletion of deployed version at $deployment_folder/$targetexe failed: $errMsg"
exit 5
}
}
}
puts stdout "copying.."
puts stdout "$sourcefolder/_build/$targetexe"
puts stdout "to:"
puts stdout "$deployment_folder/$targetexe"
after 500
file copy $sourcefolder/_build/$targetexe $deployment_folder/$targetexe
}
puts stdout "copying.."
puts stdout "$dirname/punk86.exe"
puts stdout "to:"
puts stdout "$deployment_folder/punk86.exe"
after 500
file copy $dirname/punk86.exe $deployment_folder/punk86.exe
puts stdout "done"
exit 0

43
src/modules/overtype-1.3.tm → src/modules/overtype-1.4.tm

@ -1,11 +1,12 @@
package provide [lassign {overtype 1.3} pkg ver]$pkg [namespace eval $pkg[set pkg {}] {list [variable version $ver[set ver {}]]$version}]
package provide [lassign {overtype 1.4} pkg ver]$pkg [namespace eval $pkg[set pkg {}] {list [variable version $ver[set ver {}]]$version}]
#Julian Noble <julian@precisium.com.au> - 2003
#Released under standard 'BSD license' conditions.
#
#todo - ellipsis truncation indicator for center,right
#v1.4 2023-07 - naive ansi color handling - todo - fix string range
# - need to extract and replace ansi codes?
namespace eval overtype {
namespace export *
@ -14,6 +15,28 @@ proc overtype::about {} {
return "Simple text formatting. Author JMN. BSD-License"
}
proc overtype::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 ::shellfilter::ansi::stripcodes $stripped1
} else {
#partial or not actually an ansi code.. pass it all through
return $text
}
} else {
return $text
}
}
#length of text for printing characters only
#review - unicode and other non-printing chars?
proc overtype::printing_length {str} {
string length [overtype::stripcodes $str]
}
proc overtype::left {args} {
# @c overtype starting at left (overstrike)
@ -30,8 +53,8 @@ proc overtype::left {args} {
array set opt [lrange $args 0 end-2]
set len [string length $undertext]
set overlen [string length $overtext]
set len [printing_length $undertext]
set overlen [printing_length $overtext]
set diff [expr {$overlen - $len}]
if {$diff > 0} {
if {$opt(-overflow)} {
@ -65,8 +88,8 @@ proc overtype::left2 {args} {
array set opt [lrange $args 0 end-2]
set len [string length $undertext]
set overlen [string length $overtext]
set len [printing_length $undertext]
set overlen [printing_length $overtext]
set diff [expr {$overlen - $len}]
if {$diff > 0} {
if {$opt(-overflow)} {
@ -94,8 +117,8 @@ proc overtype::centre {args} {
array set opt [lrange $args 0 end-2]
set olen [string length $overtext]
set ulen [string length $undertext]
set olen [printing_length $overtext]
set ulen [printing_length $undertext]
set diff [expr {$ulen - $olen}]
if {$diff > 0} {
set half [expr {round(int($diff / 2))}]
@ -137,8 +160,8 @@ proc overtype::right {args} {
array set opt [lrange $args 0 end-2]
set olen [string length $overtext]
set ulen [string length $undertext]
set olen [printing_length $overtext]
set ulen [printing_length $undertext]
if {$opt(-overflow)} {
return [string range $undertext 0 end-$olen]$overtext

3712
src/modules/punk-0.1.tm

File diff suppressed because it is too large Load Diff

2
src/modules/punk/config-0.1.tm

@ -38,7 +38,7 @@ namespace eval punk::config {
set exefolder [file dirname [info nameofexecutable]]
set log_folder $exefolder/logs
dict set startup scriptlib $exefolder/scriptlib
dict set startup apps $exefolder/../punkapps
dict set startup apps $exefolder/../../punkapps
if {[file exists $log_folder]} {
if {[file isdirectory $log_folder] && [file writable $log_folder]} {
dict set startup logfile_stdout $log_folder/repl-exec-stdout.txt

376
src/modules/punk/lib-0.1.tm

@ -0,0 +1,376 @@
package provide punk::lib [namespace eval punk::lib {
variable version
set version 0.1
}]
namespace eval punk::lib {
>pattern .. Create >libpattern ;#clone to a library factory
>libpattern .. Construct {args} {
var o_this
set o_this @this@
var o_last_child
set o_last_child ""
}
>libpattern .. Method version {} {
return 1.0.0
}
>libpattern .. Method aliasprefix {pfx} {
var o_this
var o_last_child
if {![string length $o_last_child]} {
error " . aliasprefix - Create library object with . new >somename first."
}
set patternmethods [$o_this .. PM]
set aliases [list]
foreach m $patternmethods {
set a ${pfx}${m}
if {[llength [info commands $a]]} {
puts stderr "WARNING - a command was already present at: $a"
}
interp alias "" $a "" [$o_last_child . $m .]
lappend aliases $a
}
return $aliases
}
>libpattern .. Method new {objcmdname} {
var o_this
set o_this @this@
var o_last_child
set nscaller [uplevel 1 [list namespace current]]
if {![string match ::* $objcmdname]} {
if {$nscaller eq "::"} {set nscaller ""}
set objcmdname ${nscaller}::$objcmdname
}
uplevel 1 [list $o_this .. Create $objcmdname]
set o_last_child $objcmdname
}
>libpattern .. Constructor {args} {
var o_this
set o_this @this@
}
>libpattern .. Clone >ls_lib
>ls_lib .. PatternMethod tail {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve tail on an empty input list" ">ls_lib . tail $args" [list argumenterror tail empty_list]
}
lrange $args 1 end
}
>ls_lib .. PatternMethod init {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve init on an empty input list" ">ls_lib . init $args" [list argumenterror init empty_list]
}
lrange $args 0 end-1
}
>ls_lib .. PatternMethod head {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve head on an empty input list" ">ls_lib . head $args" [list argumenterror head empty_list]
}
lindex $args 0
}
>ls_lib .. PatternMethod last {args} {
if {![llength $args]} {
error "argumenterror cannot retrieve last on an empty input list. Use li.index end to avoid list length check" ">ls_lib . last $args" [list argumenterror last empty_list]
}
lindex $args end
}
>ls_lib .. PatternMethod elem {val args} {
expr {$val in $args}
}
>ls_lib .. PatternMethod index {idx args} {
lindex $args $idx
}
>ls_lib .. PatternMethod range {s e args} {
lrange $args $s $e
}
#take/drop - haskell-like - but no lazy support REVIEW
#see also https://www.haskellforall.com/2022/05/why-does-haskells-take-function-accept.html
>ls_lib .. PatternMethod take {n args} {
#keep basic behaviour like Haskell ie we allow returning less than n (without error) if insufficient elements
lrange $args 0 $n-1
}
>ls_lib .. PatternMethod drop {n args} {
lrange $args $n end
}
>ls_lib . new >ls
>ls_lib . aliasprefix "ls."
#list item lib
>libpattern .. Clone >li_lib
>li_lib .. PatternMethod tail {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve tail on an empty input list" ">li_lib . tail $listdata" [list argumenterror tail empty_list]
}
lrange $listdata 1 end
}
>li_lib .. PatternMethod init {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve init on an empty input list" ">li_lib . init $listdata" [list argumenterror init empty_list]
}
lrange $listdata 0 end-1
}
>li_lib .. PatternMethod head {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve head on an empty input list" ">li_lib . head $listdata" [list argumenterror head empty_list]
}
lindex $listdata 0
}
>li_lib .. PatternMethod last {listdata} {
if {![llength $listdata]} {
error "argumenterror cannot retrieve last on an empty input list. Use li.index end to avoid list length check" ">li_lib . last $listdata" [list argumenterror last empty_list]
}
lindex $listdata end
}
>li_lib .. PatternMethod elem {val listdata} {
expr {$val in $listdata}
}
>li_lib .. PatternMethod index {idx listdata} {
lindex $listdata $idx
}
>li_lib .. PatternMethod range {s e listdata} {
lrange $listdata $s $e
}
#take/drop - haskell-like - but no lazy support REVIEW
#see also https://www.haskellforall.com/2022/05/why-does-haskells-take-function-accept.html
>li_lib .. PatternMethod take {n listdata} {
#keep basic behaviour like Haskell ie we allow returning less than n (without error) if insufficient elements
lrange $listdata 0 $n-1
}
>li_lib .. PatternMethod drop {n listdata} {
lrange $listdata $n end
}
#todo - takeWhile, dropWhile, takeWhileEnd, dropWhileEnd
>li_lib .. PatternMethod is_list_all_in_list {a b} {
package require struct::list
package require struct::set
set a_in_b [lsort [struct::set intersect [lsort -unique $a] $b ]]
return [struct::list equal [lsort $a] $a_in_b]
}
>li_lib .. PatternMethod is_list_all_ni_list {a b} {
package require struct::set
set i [struct::set intersect $a $b]
return [expr {[llength $i] == 0}]
}
>li_lib . new >li
>li_lib . aliasprefix "li."
>pattern .. Create >f_lib
>f_lib .. Construct {args} {
var o_this
set o_this @this@
var o_last_child
set o_last_child ""
}
>f_lib .. Method version {} {
return 1.0.0
}
>f_lib .. Method aliasprefix {pfx} {
var o_this
var o_last_child
if {![string length $o_last_child]} {
error ">f_lib . aliasprefix - Create library object with >f_lib . new >somename first."
}
set patternmethods [$o_this .. PM]
set aliases [list]
foreach m $patternmethods {
set a ${pfx}${m}
if {[llength [info commands $a]]} {
puts stderr "WARNING - a command was already present at: $a"
}
interp alias "" $a "" [$o_last_child . $m .]
lappend aliases $a
}
return $aliases
}
>f_lib .. Method new {objcmdname} {
var o_this
var o_last_child
set nscaller [uplevel 1 [list namespace current]]
if {![string match ::* $objcmdname]} {
if {$nscaller eq "::"} {set nscaller ""}
set objcmdname ${nscaller}::$objcmdname
}
uplevel 1 [list $o_this .. Create $objcmdname]
set o_last_child $objcmdname
}
>f_lib .. Constructor {args} {
var o_this
set o_this @this@
}
>f_lib .. PatternMethod foldl {total func sequence} {
struct::list::Lfold $sequence $total $func
}
#note: foldr is not equivalent to just doing a foldl on the reversed list
#todo - review/test/fix
>f_lib .. PatternMethod foldr {total func sequence} {
set this @this@
if {![llength $sequence]} {
return $total
}
v,h@head,t@tail.=val $sequence |h@head,t@tail> {
puts "-->$h"
$func [$this . foldr $total $func $t] $h
} <this@,func@,total@| $this $func $total
return 0
return $v
}
# reduce: simplest case - list of numbers - reduce with +
# more complex case: list of pipelines (e.g parsers) - reduce with 'andThen' operator of some sort.
>f_lib .. PatternMethod reduce {func sequence} {
struct::list::Lfold [lrange $sequence 1 end] [lindex $sequence 0] $func
}
>f_lib .. PatternMethod list_map {commandlist list} {
tailcall lmap item $list $commandlist
}
>f_lib .. PatternMethod list_unique {args} {
set list [concat {*}$args]
set d [dict create]
foreach item $list {
dict set d $item ""
}
dict keys $d
}
>f_lib .. PatternMethod list_as_lines {args} {
set list [concat {*}$args]
join $list \n
}
>f_lib .. PatternMethod list_filter_cond {itemcond listval} {
#maintenance - proc list_filter_cond
set filtered_list [list]
set binding {}
if {[info level] == 1} {
#up 1 is global
set get_vars [list info vars]
} else {
set get_vars [list info locals]
}
set vars [uplevel 1 {*}$get_vars]
set posn [lsearch $vars item]
set vars [lreplace $vars $posn $posn]
foreach v $vars {
upvar 1 $v var
if {(![array exists var]) && [info exists var]} {
lappend binding [list $v $var] ;#values captured as defaults for apply args.
}
}
#lappend binding [list item $args]
#puts stderr "binding: [join $binding \n]"
#apply [list $binding $pipescript [uplevel 1 namespace current]]
foreach item $listval {
set bindlist [list {*}$binding [list item $item]]
if {[apply [list $bindlist $itemcond [uplevel 1 namespace current]] ]} {
lappend filtered_list $item
}
}
return $filtered_list
}
>f_lib .. PatternMethod sum_llength {total listval} {
expr {$total + [llength $listval]}
}
>f_lib .. PatternMethod sum_length {total stringval} {
expr {$total + [string length $stringval]}
}
>f_lib .. PatternMethod debug {total item} {
puts stderr "incr tally: $total item: $item"
expr {$total + 1}
}
>f_lib .. PatternMethod dict_walk {d key} {
dict get $d $key
}
>f_lib .. PatternMethod sum {total num} {
expr {$total + $num}
}
>f_lib .. PatternMethod lcomp {expression args} {
#from https://wiki.tcl-lang.org/page/lcomp
set __0__ "lappend __1__ \[expr [list $expression]\]"
while {[llength $args] && [lindex $args 0] ni {for if with}} {
append __0__ " \[expr [list [lindex $args 0]]\]"
set args [lrange $args 1 end]
}
set tmpvar 2
set structure {}
set upvars {}
while {[llength $args]} {
set prefix ""
switch [lindex $args 0] {
for {
set nest [list foreach]
while {[llength $nest] == 1 || [lindex $args 0] eq "and"} {
if {[llength $args] < 4 || [lindex $args 2] ni {in inside}} {
error "wrong # operands: must be \"for\" vars \"in?side?\"\
vals ?\"and\" vars \"in?side?\" vals? ?...?"
}
switch [lindex $args 2] {
in {
lappend nest [lindex $args 1] [lindex $args 3]
} inside {
lappend nest __${tmpvar}__ [lindex $args 3]
append prefix "lassign \$__${tmpvar}__ [lindex $args 1]\n"
incr tmpvar
}}
set args [lrange $args 4 end]
}
lappend structure $nest $prefix
} if {
if {[llength $args] < 2} {
error "wrong # operands: must be \"if\" condition"
}
lappend structure [list if [lindex $args 1]] $prefix
set args [lrange $args 2 end]
} with {
if {[llength $args] < 2} {
error "wrong # operands: must be \"with\" varlist"
}
foreach var [lindex $args 1] {
lappend upvars $var $var
}
set args [lrange $args 2 end]
} default {
error "bad opcode \"[lindex $args 0]\": must be for, if, or with"
}}
}
foreach {prefix nest} [lreverse $structure] {
set __0__ [concat $nest [list \n$prefix$__0__]]
}
if {[llength $upvars]} {
set __0__ "upvar 1 $upvars; $__0__"
}
unset -nocomplain expression args tmpvar prefix nest structure var upvars
set __1__ ""
eval $__0__
return $__1__
}
>f_lib . new ::punk::lib::>f
>f_lib . aliasprefix "f."
interp alias {} >f {} ::punk::lib::>f
#Pattern-matching based functional operations
>pattern .. Create >P
>P .. Method map {pattern commandlist sequence} {
#set segment [string map [list <cmd> $commandlist] {<cmd>}]
set pipeline [list % {val $item} "|,item,$pattern>" $commandlist <item/0|]
tailcall % list $pipeline $sequence |p/0,l/1> {lmap val $l {{*}$p $val }}
}
}

631
src/modules/punk/mix-0.2.tm

@ -3,79 +3,600 @@ package provide punk::mix [namespace eval punk::mix {
set version 0.2
}]
namespace eval punk::mix {
proc runcli {args} {
if {![llength $args]} {
tailcall punk::mix::clicommands help
namespace eval punk::mix::cli {
namespace ensemble create
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 fossilize {projectname 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"
}
#new project structure - may be dedicated to one module, or contain many.
#create minimal folder structure only by specifying -modules {}
proc new {projectname args} {
lib::validate_projectname $projectname
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_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_type [dict get $opts -type]
if {$opt_type ni [lib::module_types]} {
error "pmix new error - unknown type '$opt_type' known types: [lib::module_types]"
}
set opt_layout [dict get $opts -layout]
set opt_force [dict get $opts -force]
set opt_update [dict get $opts -update]
set opt_confirm [string tolower [dict get $opts -confirm]]
set startdir [pwd]
if {[lib::is_project_dir $startdir]} {
puts stderr "Already in a project directory '$startdir' - move to a base location suitable for a new project"
return
}
set projectdir $startdir/$projectname
set tpldir [lib::mix_templates_dir]
if {[file exists $projectdir] && !($opt_force || $opt_update)} {
error "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"
} 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]} {
puts stdout "Do you want to proceed to possibly overwrite existing files in $projectdir? Y|N"
set stdin_state [fconfigure stdin]
fconfigure stdin -blocking 1
set answer [gets stdin]
fconfigure stdin -blocking [dict get $stdin_state -blocking]
if {$answer ne "Y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y to procedd) 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"
}
#todo - lookup config for .fossil repo location. For now use current dir.
if {![file exists $startdir/$projectname.fossil]} {
puts stdout "Initialising fossil repo: $startdir/$projectname.fossil"
set fossilinit [runx -n fossil init $projectname.fossil -project-name $projectname]
if {[dict get $fossilinit exitcode] != 0} {
puts stderr "fossil init failed:"
puts stderr [dict get $fossilinit stderr]
return
} else {
tailcall punk::mix::clicommands {*}$args
puts stdout "fossil init result:"
puts stdout [dict get $fossilinit stdout]
}
} else {
puts stdout "NOTICE: $startdir/$projectname.fossil already exists"
if {!($opt_force || $opt_update)} {
return
}
}
file mkdir $projectdir
set layout_dir $tpldir/layouts/$opt_layout
if {$opt_force} {
lib::copy_files_from_source_to_base $layout_dir $projectdir -force $opt_force
#file copy -force $layout_dir $projectdir
} else {
lib::copy_files_from_source_to_base $layout_dir $projectdir
}
}
oo::object create punk::mix::clicommands
#expect this in all templates? - todo make these substitutions independent of specific paths and filenames?
set readme_file $projectdir/src/README.md
if {[file exists $readme_file]} {
set fd [open $readme_file r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd
set data [string map [list %project% $projectname] $data]
set fdout [open $readme_file w]; fconfigure $fdout -translation binary; puts -nonewline $fdout $data; close $fdout
} else {
puts stderr "warning: Missing $projectdir/src/README.md"
}
cd $projectdir
oo::objdefine punk::mix::clicommands {
#namespace ensemble configure [namespace current] -unknown punk::mix::clicommands::_unknown
foreach m $opt_modules {
newmodule $m -project $projectname -type $opt_type -force $opt_force
}
method set_alias {cmdname} {
uplevel #0 [list interp alias {} $cmdname {} punk::mix::runcli]
cd $projectdir
if {![file exists $projectdir/_FOSSIL_]} {
set first_fossil 1
set fossilopen [runx -n fossil open ../$projectname.fossil -k]
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]
}
#method _unknown {ns args} {
# puts stderr "arglen:[llength $args]"
# puts stdout "_unknown '$ns' '$args'"
#
# list punk::mix::clicommands::help {*}$args
#}
} else {
set first_fossil 0
}
set fossiladd [runx -n fossil add --dotfiles .]
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
set fossilcommit [run -n fossil 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"
}
interp alias {} ::punk::mix::cli::project {} ::punk::mix::cli::new
#require current dir when calling to be the projectdir, or
proc newmodule {module args} {
set year [clock format [clock seconds] -format %Y]
set defaults [list -project \uFFFF -type \uFFFF -version 0.1.0 -force 0 -license <unspecified>]
set opts [dict merge $defaults $args]
set opt_project [dict get $opts -project]
set opt_version [dict get $opts -version]
set opt_license [dict get $opts -license]
if {[string first - $module]> 0} {
set vparts [lassign [split $module -] modulename]
set mversion [join $vparts -] ;# (- not supported in tcl versions for 8.7 - but possibly part of 9+ if semver implemented)
if {![lib::is_valid_tm_version $mversion]} {
error "pmix newmodule error - unable to determine modulename-version from supplied value '$module'"
}
if {[package vcompare $mversion $opt_version] > 0} {
set opt_version $mversion; #module parameter has higher value than -version
}
} else {
set modulename $module
}
lib::validate_modulename $modulename "mix newmodule name"
set testdir [pwd]
if {[file tail $testdir] in [list "bin" "lib" "modules" "src"]} {
set testdir [file dirname $testdir]
if {[file tail $testdir] eq "src"} {
set testdir [file dirname $testdir]
}
}
set projectdir $testdir
if {![lib::is_project_dir $projectdir]} {
error "newmodule unable to create module in projectdir:$projectdir - directory doesn't appear to be an existing project"
}
if {$opt_project == "\uFFFF"} {
set projectname [file tail $projectdir]
} else {
set projectname $opt_project
if {$projectname ne [file tail $projectdir]} {
error "newmodule -project '$opt_project' doesn't match detected projectname '$projectname' at path: $projectdir"
}
}
set opt_type [dict get $opts -type]
if {$opt_type eq "\uFFFF"} {
set opt_type [lindex [lib::module_types] 0] ;#default to plain
}
if {$opt_type ni [lib::module_types]} {
error "mix newmodule - error - unknown -type '$opt_type' known-types: [lib::module_types]"
}
set subpath [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 tpldir [lib::mix_templates_dir]
set magicversion [lib::magic_tm_version] ;#deliberately large so given load-preference when testing
set fd [open $tpldir/module/module_buildversion.txt r]; set filedata [read $fd]; close $fd
set filedata [string map [list %Major.Minor.Level% $opt_version] $filedata]
set fd [open $modulefolder/${moduletail}-buildversion.txt w]
fconfigure $fd -translation binary
puts -nonewline $fd $filedata
close $fd
set tpldir [lib::mix_templates_dir]
set fd [open $tpldir/module/module_template-0.0.1.tm r]; set filedata [read $fd]; close $fd
set filedata [string map [list %pkg% $modulename %year% $year %license% $opt_license] $filedata]
set fd [open $modulefolder/${moduletail}-$magicversion.tm w]
fconfigure $fd -translation binary
puts -nonewline $fd $filedata
close $fd
}
proc make {args} {
set startdir [pwd]
if {[lib::is_project_dir $startdir]} {
cd $startdir/src
set sourcefolder $startdir/src
} else {
set sourcefolder $startdir
}
if {([file tail $sourcefolder] ne "src") || (![file exists $sourcefolder/make.tcl])} {
puts stderr "mix make must be run from src folder containing make.tcl - unable to proceed"
return false
}
#use run so that stdout visible as it goes
set exitinfo [run [info nameofexecutable] $sourcefolder/make.tcl]
set exitcode [dict get $exitinfo exitcode]
if {$exitcode != 0} {
puts stderr "FAILED with exitcode $exitcode"
return false
} else {
puts stdout "OK make finished "
return true
}
}
proc libexample {} {
set result [lib::libfunc1 test]
return $result
}
namespace eval lib {
proc libfunc1 {args} {
return libfunc1-$args
}
proc module_types {} {
#first in list is default for unspecified -type when creating new module
return [list plain tarjar zipkit]
}
proc module_subpath {modulename} {
set modulename [string trim $modulename :]
set nsq [namespace qualifiers $modulename]
return [string map [list :: /] $nsq]
}
proc validate_modulename {modulename {name_description modulename}} {
validate_name_not_empty_or_spaced $modulename $name_description
set testname [string map [list :: ""] $modulename]
if {[string first : $testname] >=0} {
error "$name_description '$modulename' can only contain paired colons"
}
set badchars [list - "$" "?" "*"]
foreach bc $badchars {
if {[string first $bc $modulename] >= 0} {
error "$name_description '$modulename' can not contain character '$bc'"
}
}
return $modulename
}
proc validate_projectname {projectname {name_description projectname}} {
validate_name_not_empty_or_spaced $projectname $name_description
set reserved_words [list etc lib bin modules src doc man html tests]
if {$projectname in $reserved_words } {
error "$name_description '$projectname' cannot be one of reserved_words: $reserved_words"
}
if {[string first "::" $projectname] >= 0} {
error "$name_description '$projectname' cannot contain namespace separator '::'"
}
return $projectname
}
proc validate_name_not_empty_or_spaced {name {name_description name}} {
if {![string length $name]} {
error "$name_description cannot be empty"
}
if {[string length [string map [list " " "" \n "" \r "" \t ""] $name]] != [string length $name]} {
error "$name_description cannot contain whitespace"
}
return $name
}
proc is_project_dir {path} {
#review - find a reliable simple mechanism. Noting we have projects based on different templates.
#Should there be a specific required 'project' file of some sort?
#exclude some known places we wouldn't want to put a project
set normpath [file normalize $path]
set unwise_paths [list "/usr/local" "/usr/local/bin" "/usr/local/lib" "c:/windows"]
if {[string tolower $normpath] in $unwise_paths} {
return 0
}
method new {name} {
set curdir [pwd]
if {[file exists $curdir/$name]} {
error "Unable to create new project at $curdir/$name - file/folder already exists"
if {![file exists $path/src]} {
return 0
}
set base $curdir/$name
file mkdir $base
file mkdir $base/src
file mkdir $base/modules
#test for file/folder items indicating fossil or git workdir base
#todo - review .fossil check.. workdir checkout dir can be unrelate to *.fossil location - need to check fossil config?
set fossil_items [glob -nocomplain -dir $path *.fossil _FOSSIL_]
set git_items [glob -nocomplain -dir $path .gitignore .git]
if {([llength $fossil_items] < 1) && ([llength $git_items] < 1) } {
return 0
}
return 1
}
proc mix_templates_dir {} {
set provide_statement [package ifneeded punk::mix [package require punk::mix]]
set tmdir [file dirname [lindex $provide_statement end]]
set tpldir $tmdir/mix/templates
if {![file exists $tpldir]} {
error "punk::mix::lib::mix_templates_dir unable to locate mix templates folder at '$tpldir'"
}
return $tpldir
}
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
}
}
method 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>
#' ```
#'
#'
#todo - review. Check Tcl's exact requirements here
# assume we can have things like: 1.1a2 2.2.b4
proc is_valid_tm_version1 {versionpart} {
#review - regexp from https://wiki.tcl-lang.org/page/Package+MetaData+Fields
#page notes that 'valid version numbers can be decoded via the following.."
#regexp {([0-9]+)\.([0-9]+)\.?([ab])?\.?([0-9]*)} $ver => major minor maturity level
#but that doesn't rule out invalid version numbers being passed by this and causing issues with version comparisons, package loading etc.
set versionsegments [split $versionpart .]
if {![string is integer -strict [lindex $versionsegments 0]]} {
return 0
}
#rudimentary check on the tail..
#reviewed briefly 2023-07 - need to support e.g 2.5.b.5 ?
#Note that package vcompare in tcl 8.7a5 doesn't support 2.5.b.5
foreach tailpart [lrange $versionsegments 1 end] {
if {![string is integer -strict $tailpart]} {
#extremely loose check..
#pass anything with an a or b for now..
#review to see if tcl tm system allows semver style x.y.z-beta etc or if we should lock it down
#need to take into account how tcl compares/orders version numbers.
if {(![string first a $tailpart] >= 0) && (![string first b $tailpart] >=0)} {
return 0
}
}
}
return 1
}
set commands [namespace export]
set helpstr ""
append helpstr "commands:\n"
foreach cmd $commands {
append helpstr " $cmd"
#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-]+)*))?$}
}
proc magic_tm_version {} {
return 999999.0a1.0 ;#deliberately large so given load-preference when testing
}
proc copy_modules_from_source_to_base {srcdir basedir args} {
set defaults [list -glob *.tm -antiglob [list "*[magic_tm_version]*"] ]
set opts [dict merge $defaults $args]
copy_files_from_source_to_base $srcdir $basedir {*}$opts
}
proc copy_nonmodules_from_source_to_base {srcdir basedir args} {
#set keys [dict keys $args]
set defaults [list -glob * -antiglob [list "*.tm" "*-buildversion.txt"]]
set opts [dict merge $defaults $args]
copy_files_from_source_to_base $srcdir $basedir {*}$opts
}
proc copy_files_from_source_to_base {srcdir basedir args} {
set defaults [list -subdirlist {} -glob * -antiglob [list "*[magic_tm_version]*" "*-buildversion.txt"] -force 0]
set opts [dict merge $defaults $args]
set subdirlist [dict get $opts -subdirlist]
set fileglob [dict get $opts -glob]
set antiglobs [dict get $opts -antiglob]
set force [dict get $opts -force]
set copied_files [list]
set candidate_list [glob -nocomplain -dir $srcdir -type f -tail $fileglob]
set hidden_candidate_list [glob -nocomplain -dir $srcdir -types {hidden f} -tail $fileglob]
foreach h $hidden_candidate_list {
if {$h ni $candidate_list} {
lappend candidate_list $h
}
}
set match_list [list]
foreach m $candidate_list {
set suppress 0
foreach anti $antiglobs {
puts stderr "anti: $anti vs m:$m"
if {[string match $anti $m]} {
set suppress 1
break
}
}
if {$suppress == 0} {
lappend match_list $m
}
}
if {![file exists $basedir]} {
error "copy_files_from_source_to_base basedir:'$basedir' doesn't exist"
}
if {[llength $subdirlist] == 0} {
set target_module_dir $basedir
} else {
set target_module_dir $basedir/[file join {*}$subdirlist]
}
foreach m $match_list {
puts stdout "copying file $srcdir/$m to $target_module_dir"
if {$force} {
file copy -force $srcdir/$m $target_module_dir
} else {
if {![file exists $target_module_dir/$m]} {
file copy $srcdir/$m $target_module_dir
} else {
puts stderr "Skipping file copy $m target $target_module_dir/$m already exists (use -force 1 to overwrite)"
}
}
lappend copied_files $srcdir/$m
}
set subdirs [glob -nocomplain -dir $srcdir -type d -tail *]
set hiddensubdirs [glob -nocomplain -dir $srcdir -type {hidden d} -tail *]
foreach h $hiddensubdirs {
if {$h ni $subdirs} {
lappend subdirs $h
}
return $helpstr
}
#puts stderr "subdirs: $subdirs"
foreach d $subdirs {
if {[string match "#*" $d] || ($d eq "_aside") || ($d eq ".git")} {
continue
}
if {![file exists $target_module_dir/$d]} {
file mkdir $target_module_dir/$d
}
lappend copied_files {*}[copy_files_from_source_to_base $srcdir/$d $basedir -subdirlist [list {*}$subdirlist $d] -glob $fileglob -antiglob $antiglobs -force $force]
}
return $copied_files
}
proc build_modules_from_source_to_base {srcdir basedir args} {
set defaults [list -subdirlist {} -glob *.tm]
set opts [dict merge $defaults $args]
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 magicversion [magic_tm_version] ;#deliberately large so given load-preference when testing
set module_list [list]
set src_modules [glob -nocomplain -dir $srcdir -type f -tail $fileglob]
if {![file exists $basedir]} {
error "build_modules_from_source_to_base from srcdir: '$srcdir' to basedir:'$basedir' doesn't exist or is empty"
}
if {![file exists $srcdir]} {
error "build_modules_from_source_to_base from srcdir:'$srcdir' doesn't exist or is empty"
}
if {[llength $subdirlist] == 0} {
set target_module_dir $basedir
} else {
set target_module_dir $basedir/[file join {*}$subdirlist]
}
foreach m $src_modules {
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 $srcdir/$basename-buildversion.txt
if {![file exists $versionfile]} {
puts stderr "ERROR: Missing buildversion text file: $versionfile"
}
set fd [open $versionfile r]; set data [read $fd]; close $fd
set ln0 [lindex [split $data \n] 0]
set ln0 [string trim $ln0]; set ln0 [string trim $ln0 \r]
if {![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 $srcdir/#tarjar-$basename-$magicversion]} {
if {[file exists $srcdir/#tarjar-$basename-$magicversion/DESCRIPTION.txt]} {
} else {
}
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 $srcdir/#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
}
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 $srcdir/#tarjar-$basename-${magicversion}#]} {
puts stderr "Warning: found 'capped' folder #tarjar-$basename-${magicversion}# - No attempt being made to update version in description.txt"
}
set target $target_module_dir/$basename-$module_build_version.tm
puts stdout "copying module $srcdir/$m to $target as version: $module_build_version ([file tail $target])"
set fd [open $srcdir/$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
}
continue
}
if {![is_valid_tm_version $tmfile_versionsegment]} {
#last segment doesn't look even slightly versiony - fail.
puts stderr "ERROR: Unable to confirm file $srcdir/$m is a reasonably versioned .tm module - ABORTING."
exit 1
}
puts stderr "copying already versioned module $srcdir/$m to $target_module_dir"
file copy -force $srcdir/$m $target_module_dir
lappend module_list $srcdir/$m
}
set subdirs [glob -nocomplain -dir $srcdir -type d -tail *]
#puts stderr "subdirs: $subdirs"
foreach d $subdirs {
if {[string match "#*" $d] || ($d eq "_aside") || ($d eq ".git")} {
continue
}
if {![file exists $target_module_dir/$d]} {
file mkdir $target_module_dir/$d
}
lappend module_list {*}[build_modules_from_source_to_base $srcdir/$d $basedir -subdirlist [list {*}$subdirlist $d] -glob $fileglob]
}
return $module_list
}
}
}
unexport destroy
namespace eval punk::mix::cli {
variable default_command help
package require punk::mix::base
package require punk::overlay
punk::overlay::custom_from_base [namespace current] ::punk::mix::base
}
#package require punk
#package require punk::mix_extension
#punk::ensemble::extend punk::mix::clicommands punk::mix_extension

216
src/modules/punk/mix/base-0.1.tm

@ -0,0 +1,216 @@
package provide punk::mix::base [namespace eval punk::mix::base {
variable version
set version 0.1
}]
#base internal plumbing functions
namespace eval punk::mix::base {
proc set_alias {cmdname args} {
extension@@opts/@?@-extension,args@@args= [_split_args $args]
uplevel #0 [list interp alias {} $cmdname {} punk::mix::base::_cli -extension $extension]
}
proc _cli {args} {
extension@@opts/@?@-extension,args@@args= [_split_args $args]
if {![string length $extension]} {
set extension [namespace qualifiers [lindex [info level -1] 0]]
}
puts stderr ">>> extension:$extension"
if {![llength $args]} {
if {[info exists ${extension}::default_command]} {
tailcall $extension [set ${extension}::default_command]
}
tailcall $extension
} else {
tailcall $extension {*}$args
}
}
proc _unknown {ns args} {
extension@@opts/@?@-extension,args@@args= [_split_args $args]
if {![string length $extension]} {
set extension [namespace qualifiers [lindex [info level -1] 0]]
}
puts stderr "arglen:[llength $args]"
puts stdout "_unknown '$ns' '$args'"
set d_commands [get_commands -extension $extension]
set all_commands [list {*}[dict get $d_commands main] {*}[dict get $d_commands base]]
error "Unknown subcommand \"[lindex $args 0]\": must be one of: $all_commands" "punk::mix::base _unknown $ns $args" [list unknown_ensemble_subcommand ensemble punk::mix::base]
}
proc _redirected {from_ns subcommand args} {
puts stderr "_redirected from_ns: $from_ns subcommand:$subcommand args:$args"
set pname [namespace current]::$subcommand
if {$pname in [info procs $pname]} {
set argnames [info args $pname]
puts stderr "$subcommand argnames: $argnames"
if {[lindex $argnames end] eq "args"} {
set pos_argnames [lrange $argnames 0 end-1]
} else {
set pos_argnames $argnames
}
set argvals [list]
set numargs [llength $pos_argnames]
if {$numargs > 0} {
set argvals [lrange $args 0 $numargs-1]
set args [lrange $args $numargs end]
}
if {[llength $argvals] < $numargs} {
error "wrong # args: $from_ns $subcommand requires args: $pos_argnames"
}
tailcall [namespace current] $subcommand {*}$argvals {*}$args -extension $from_ns
} else {
tailcall [namespace current] $subcommand {*}$args -extension $from_ns
}
}
proc _split_args {arglist} {
#don't assume arglist is fully paired.
set posn [lsearch $arglist -extension]
set opts [list]
if {$posn >= 0} {
if {$posn+2 <= [llength $arglist]} {
set opts [list -extension [lindex $arglist $posn+1]]
set argsremaining [lreplace $arglist $posn $posn+1]
} else {
#no value supplied to -extension
error "punk::mix::base::_split_args - no value found for option '-extension'. Supply a value or omit the option."
}
} else {
set argsremaining $arglist
}
return [list opts $opts args $argsremaining]
}
}
#base API (potentially overridden functions - may also be called from overriding namespace)
#commands should either handle or silently ignore -extension <namespace/ensemble>
namespace eval punk::mix::base {
namespace ensemble create
namespace export help dostuff get_commands set_alias
namespace ensemble configure [namespace current] -unknown punk::mix::base::_unknown
proc get_commands {args} {
extension@@opts/@?@-extension,args@@args= [_split_args $args]
if {![string length $extension]} {
set extension [namespace qualifiers [lindex [info level -1] 0]]
}
set maincommands [list]
#extension may still be blank e.g if punk::mix::base::get_commands called directly
if {[string length $extension]} {
set nsmain $extension
puts stdout "get_commands nsmain: $nsmain"
set parentpatterns [namespace eval $nsmain [list namespace export]]
set nscommands [list]
foreach p $parentpatterns {
lappend nscommands {*}[info commands ${nsmain}::$p]
}
foreach c $nscommands {
set cmd [namespace tail $c]
lappend maincommands $cmd
}
set maincommands [lsort $maincommands]
}
set nsbase [namespace current]
set basepatterns [namespace export]
puts stdout "basepatterns:$basepatterns"
set nscommands [list]
foreach p $basepatterns {
lappend nscommands {*}[info commands ${nsbase}::$p]
}
set basecommands [list]
foreach c $nscommands {
set cmd [namespace tail $c]
if {$cmd ni $maincommands} {
lappend basecommands $cmd
}
}
set basecommands [lsort $basecommands]
return [list main $maincommands base $basecommands]
}
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>
#' ```
#'
#'
#extension.= @@opts/@?@-extension,args@@args=>. [_split_args $args] {|
# >} inspect -label a {|
# >} .=e>end,data>end pipeswitch {
# pipecase ,0/1/#= $switchargs {|
# e/0
# >} .=>. {set e}
# pipecase /1,1/1/#= $switchargs
#} |@@ok/result> <e/0| [namespace qualifiers [lindex [info level -1] 0]]
extension@@opts/@?@-extension,args@@args= [_split_args $args]
if {![string length $extension]} {
set extension [namespace qualifiers [lindex [info level -1] 0]]
}
#puts stderr "-1:[info level -1]"
set command_info [punk::mix::base::get_commands -extension $extension]
set subhelp1 [lindex $args 0]
if {[string length $subhelp1]} {
if {$subhelp1 in [dict get $command_info main]} {
set procname ${extension}::$subhelp1
if {$procname in [info procs $procname]} {
set argnames [info args $procname]
} else {
set argnames "(No info available)"
}
return "$subhelp1 $argnames"
} elseif {$subhelp1 in [dict get $command_info base]} {
set procname [namespace current]::$subhelp1
if {$procname in [info procs $procname]} {
set argnames [info args $procname]
} else {
set argnames "(No info available)"
}
return "$subhelp1 $argnames"
}
}
set helpstr ""
append helpstr "commands:\n"
foreach {source cmdlist} $command_info {
append helpstr \n " $source"
foreach cmd $cmdlist {
append helpstr \n " - $cmd"
}
}
return $helpstr
}
proc dostuff {args} {
extension@@opts/@?@-extension,args@@args= [_split_args $args]
puts stdout "base doingstuff-with-args:'$args'-in-namespace:'[namespace current]'"
}
}

13
src/modules/punk/mix/templates/layouts/project/.fossil-custom/mainmenu

@ -0,0 +1,13 @@
Home /home * {}
Timeline /timeline {o r j} {}
Files /dir?ci=tip oh desktoponly
Branches /brlist o wideonly
Tags /taglist o wideonly
Forum /forum {@2 3 4 5 6} wideonly
Chat /chat C wideonly
Tickets /ticket r wideonly
Wiki /wiki j wideonly
Download /download * {}
Admin /setup {a s} desktoponly
Logout /logout L wideonly
Login /login !L wideonly

3
src/modules/punk/mix/templates/layouts/project/.fossil-settings/empty-dirs

@ -0,0 +1,3 @@
src
src/deps
src/modules

21
src/modules/punk/mix/templates/tpl_.gitignore → src/modules/punk/mix/templates/layouts/project/.fossil-settings/ignore-glob

@ -1,10 +1,23 @@
.git
bin
lib
#The directory for compiled/built Tcl modules
/modules/
modules
#Temporary files e.g from tests
/tmp/
tmp
/logs/
logs
_aside
_build
#Built documentation
html
man
md
doc
test*
#Built tclkits (if any)
punk*.exe
@ -12,3 +25,5 @@ tcl*.exe
#miscellaneous editor files etc
*.swp
todo.txt

38
src/modules/punk/mix/templates/layouts/project/.gitignore vendored

@ -0,0 +1,38 @@
/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
#ignore fossil database files (but keep .fossil-settings even if fossil not being used at your site)
_FOSSIL_
*.fossil
#miscellaneous editor files etc
*.swp
todo.txt

14
src/modules/punk/mix/templates/layouts/project/src/README.md

@ -0,0 +1,14 @@
%project%
==============================
+
+
About
------------------------------
+
+
+

195
src/modules/punk/mix/templates/layouts/project/src/make.tcl

@ -0,0 +1,195 @@
# tcl
#
#make any tclkits and modules in src and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder.
#It is assumed the src folder has been placed somewhere where appropriate
#(e.g not in /usr or c:/ - unless you intend it to directly make and place folders and files in those locations)
package require punk::mix
if {[lsearch $::argv -k] >= 0} {
set forcekill 1
} else {
set forcekill 0
}
puts stdout "::argv $::argv"
set sourcefolder [file normalize [file dirname [info script]]]
# ----------------------------------------
set target_modules_base [file dirname $sourcefolder]/modules
file mkdir $target_modules_base
#external modules first - and any supporting files - no 'building' required
set copied [punk::mix::cli::lib::copy_files_from_source_to_base $sourcefolder/deps $target_modules_base -force 1]
puts stderr "Copied [llength $copied] dependencies"
set src_module_dir $sourcefolder/modules
#modules and associated files belonging to this package/app
set copied [punk::mix::cli::lib::build_modules_from_source_to_base $src_module_dir $target_modules_base -glob *.tm] ;#will only accept a glob ending in .tm
puts stderr "Copied [llength $copied] app modules"
set copied [punk::mix::cli::lib::copy_nonmodules_from_source_to_base $src_module_dir $target_modules_base -force 1]
# ----------------------------------------
set vfs_folders [glob -nocomplain -dir $sourcefolder -types d -tail *.vfs]
if {![llength $vfs_folders]} {
puts stdout "No .vfs folders found at '$sourcefolder' - no kits to build"
puts stdout " -done- "
exit 0
}
file mkdir $sourcefolder/_build
if {[catch {exec sdx help} errM]} {
puts stderr "FAILED to find usable sdx command - check that sdx executable is on path"
puts stderr "err: $errM"
exit 1
}
#find runtime - only supports one for now.. REVIEW
set rtfolder $sourcefolder/runtime
set runtimes [glob -nocomplain -dir $rtfolder -types {f x} -tail *]
if {![llength $runtimes]} {
puts stderr "No executable runtimes found in $rtfolder - unable to build any .vfs folders into executables."
exit 2
}
if {[llength $runtimes] > 1} {
puts stderr "Found multiple runtimes in $rtfolder ($runtimes) - unable to proceed - currently limited to one."
exit 3
}
set runtimefile [lindex $runtimes 0]
puts stdout "Found [llength $vfs_folders] .vfs folders - building executable for each..."
foreach vfs $vfs_folders {
set vfsname [file rootname $vfs]
puts stdout " Processing vfs $sourcefolder/$vfs"
puts stdout " ------------------------------------"
if {[file exists $sourcefolder/_build/$vfsname]} {
puts stderr "deleting existing $sourcefolder/_build/$vfsname"
file delete $sourcefolder/_build/$vfsname
}
puts stdout "building $vfsname with sdx.."
if {[catch {
exec sdx wrap _build/$vfsname -runtime runtime/$runtimefile -verbose
} result]} {
puts stderr "sdx wrap _build/$vfsname -runtime runtime/$runtimefile -verbose failed with msg: $result"
} else {
puts stdout "ok - finished sdx"
set separator [string repeat = 40]
puts stdout $separator
puts stdout $result
puts stdout $separator
}
if {![file exists $sourcefolder/_build/$vfsname]} {
puts stderr "|err> build didn't seem to produce output at $sourcefolder/_build/$vfsname"
exit 2
}
if {$::tcl_platform(platform) eq "windows"} {
set pscmd "tasklist"
} else {
set pscmd "ps"
}
if {![catch {
exec $pscmd | grep $vfsname
} still_running]} {
puts stdout "found $vfsname instances still running\n"
set count_killed 0
foreach ln [split $still_running \n] {
puts stdout " $ln"
if {$::tcl_platform(platform) eq "windows"} {
set pid [lindex $ln 1]
if {$forcekill} {
set killcmd [list taskkill /F /PID $pid]
} else {
set killcmd [list taskkill /PID $pid]
}
} else {
set pid [lindex $ln 0]
#review!
if {$forcekill} {
set killcmd [list kill -9 $pid]
} else {
set killcmd [list kill $pid]
}
}
puts stdout " pid: $pid (attempting to kill now using '$killcmd')"
if {[catch {
exec {*}$killcmd
} errMsg]} {
puts stderr "$killcmd returned an error:"
puts stderr $errMsg
puts stderr "(try '[info script] -k' option to force kill)"
exit 4
} else {
puts stderr "$killcmd ran without error"
incr count_killed
}
}
if {$count_killed > 0} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
}
} else {
puts stderr "Ok.. no running '$vfsname' processes found"
}
if {$::tcl_platform(platform) eq "windows"} {
set targetexe ${vfsname}.exe
} else {
set targetexe $vfsname
}
if {[file exists $sourcefolder/_build/$targetexe]} {
puts stderr "deleting existing $sourcefolder/_build/$targetexe"
if {[catch {
file delete $sourcefolder/_build/$targetexe
} msg]} {
puts stderr "Failed to delete $sourcefolder/_build/$targetexe"
exit 4
}
}
if {$::tcl_platform(platform) eq "windows"} {
file rename $sourcefolder/_build/$vfsname $sourcefolder/_build/${vfsname}.exe
}
after 200
set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder
if {[file exists $deployment_folder/$targetexe]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetexe"
if {[catch {
file delete $deployment_folder/$targetexe
} errMsg]} {
puts stderr "deletion of deployed version at $deployment_folder/$targetexe failed: $errMsg"
exit 5
}
}
puts stdout "copying.."
puts stdout "$sourcefolder/_build/$targetexe"
puts stdout "to:"
puts stdout "$deployment_folder/$targetexe"
after 500
file copy $sourcefolder/_build/$targetexe $deployment_folder/$targetexe
}
puts stdout "done"
exit 0

3
src/modules/punk/mix/templates/module/module_buildversion.txt

@ -0,0 +1,3 @@
%Major.Minor.Level%
#First line must be a semantic version number
#all other lines are ignored.

0
src/modules/punk/mix/templates/tpl_clitemplate-0.1.tm → src/modules/punk/mix/templates/module/module_clitemplate-0.0.1.tm

0
src/modules/punk/mix/templates/tpl_description.txt → src/modules/punk/mix/templates/module/module_description.txt

51
src/modules/punk/mix/templates/module/module_template-0.0.1.tm

@ -0,0 +1,51 @@
# -*- 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
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
##e.g package require frobz
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval %pkg% {
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide %pkg% [namespace eval %pkg% {
variable version
set version 999999.0a1.0
}]
return

12
src/modules/punk/mix_extension-0.1.tm

@ -1,12 +0,0 @@
package provide punk::mix_custom [namespace eval punk::mix_custom {
variable version
set version 0.1
}]
namespace eval punk::mix_custom {
proc dostuff {args} {
puts stdout doingstuff-$args
}
}

62
src/modules/punk/overlay-0.1.tm

@ -0,0 +1,62 @@
package provide [lindex [list [set ver [join [lassign [split [ file tail [file rootname [info script] ]] -] pkg] -]] $pkg] 1]\
[namespace eval $pkg[unset pkg] {list [variable version $ver[unset ver]]$version}]
#package provide [lassign {overtype 1.4} pkg ver]$pkg [namespace eval $pkg[set pkg {}] {list [variable version $ver[set ver {}]]$version}]
namespace eval ::punk::overlay {
#based *loosely* on: wiki.tcl-lang.org/page/ensemble+extend
# extend an ensemble-like routine with the routines in some namespace
proc custom_from_base {routine base} {
if {![string match ::* $routine]} {
set resolved [uplevel 1 [list ::namespace which $routine]]
if {$resolved eq {}} {
error [list {no such routine} $routine]
}
set routine $resolved
}
set routinens [namespace qualifiers $routine]
if {$routinens eq {::}} {
set routinens {}
}
set routinetail [namespace tail $routine]
if {![string match ::* $base]} {
set base [uplevel 1 [
list [namespace which namespace] current]]::$base
}
if {![namespace exists $base]} {
error [list {no such namespace} $base]
}
set base [namespace eval $base [
list [namespace which namespace] current]]
#while 1 {
# set renamed ${routinens}::${routinetail}_[info cmdcount]
# if {[namespace which $renamed] eq {}} break
#}
namespace eval $routine [
list namespace ensemble configure $routine -unknown [
list apply {{base ensemble subcommand args} {
list ${base}::_redirected $ensemble $subcommand
}} $base
]
]
namespace eval $routine {
set exportlist [list]
foreach cmd [info commands [namespace current]::*] {
set c [namespace tail $cmd]
if {![string match _* $c]} {
lappend exportlist $c
}
}
namespace export {*}$exportlist
}
return $routine
}
}

1317
src/modules/punk/repl-0.1.tm

File diff suppressed because it is too large Load Diff

2
src/modules/punkapp-0.1.tm

@ -1,3 +1,5 @@
#utilities for punk apps to call
package provide punkapp [namespace eval punkapp {
variable version
set version 0.1

10
src/modules/shellfilter-0.1.8.tm

@ -182,6 +182,9 @@ namespace eval shellfilter::ansi {
}
namespace eval shellfilter::chan {
set testobj ::shellfilter::chan::var
if {$testobj ni [info commands $testobj]} {
oo::class create var {
variable o_datavar
variable o_trecord
@ -777,6 +780,7 @@ namespace eval shellfilter::chan {
}
}
}
}
# ----------------------------------------------------------------------------
@ -981,6 +985,10 @@ namespace eval shellfilter::stack {
}
proc remove {pipename remove_id} {
variable pipelines
if {![dict exists $pipelines $pipename]} {
puts stderr "WARNING: shellfilter::stack::remove pipename '$pipename' not found in pipelines dict: '$pipelines' [info level -1]"
return
}
set stack [dict get $pipelines $pipename stack]
set localchan [dict get $pipelines $pipename device localchan]
set posn 0
@ -1862,7 +1870,7 @@ namespace eval shellfilter {
#script result
set exitinfo [list result [uplevel #0 [list eval $commandlist]]]
} errMsg]} {
set exitinfo [list error "$errMsg"]
set exitinfo [list error "$errMsg" errorInfo "$::errorInfo"]
}
}

BIN
src/modules/tarjar-2.3.tm

Binary file not shown.

1199
src/punk86.vfs/lib/app-punk/repl.tcl

File diff suppressed because it is too large Load Diff

24
src/punk86.vfs/lib/app-shellspy/shellspy.tcl

@ -35,11 +35,18 @@ package provide app-shellspy 1.0
set original_tm_list [tcl::tm::list]
tcl::tm::remove {*}$original_tm_list
#add dir outside of tclkit/exe so we can override with higher versions if necessary without rebuilding
set m_dir [file normalize [file join [file dirname [info nameofexecutable]] modules]]
tcl::tm::add $m_dir
set m_dir [file normalize [file join [file dirname [info script]] ../../../modules]]
if {[string match "*.vfs/*" [info script]]} {
#src/xxx.vfs/lib/app-punk/repl.tcl
#we assume if calling directly into .vfs that the user would prefer to use src/modules - so go up 4 levels
set m_dir [file dirname [file dirname [file dirname [file dirname [info script]]]]]/modules
} else {
#add dir outside of tclkit/exe so we can override with higher versions if necessary without rebuilding
# .../bin/punkXX.exe look for ../modules (i.e modules folder at same level as bin folder)
set m_dir [file normalize [file join [file dirname [file dirname [info nameofexecutable]]] modules]]
}
tcl::tm::add $m_dir
#set m_dir [file normalize [file join [file dirname [info script]] ../../../modules]]
#tcl::tm::add $m_dir
#experiment - todo make a flag for it if it's useful
#Middle cap for direct dispatch without flagcheck arg processing or redirections or REPL.
@ -542,8 +549,13 @@ source [file normalize $scriptname]
}]
set repl_lines "package require app-punk\n"
append repl_lines "repl::start stdin\n"
set repl_lines ""
#append repl_lines {puts stderr "starting repl [chan names]"} \n
#append repl_lines {puts stderr "stdin [chan configure stdin]"} \n
append repl_lines {package require punk::repl} \n
append repl_lines {repl::start stdin} \n
#append repl_lines {puts stdout "shutdown message"} \n
if {$replwhen eq "repl_first"} {
#we need to cooperate with the repl to get the script to run on exit

Loading…
Cancel
Save