Browse Source

update bootsupport,vfs,project_layouts for punk::args and punk::safe

master
Julian Noble 4 months ago
parent
commit
e85481ad8c
  1. 74
      src/bootsupport/modules/punk-0.1.tm
  2. 36
      src/bootsupport/modules/punk/aliascore-0.1.0.tm
  3. 576
      src/bootsupport/modules/punk/args-0.1.0.tm
  4. 2
      src/bootsupport/modules/punk/fileline-0.1.0.tm
  5. 81
      src/bootsupport/modules/punk/lib-0.1.1.tm
  6. 2
      src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  7. 7
      src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  8. 40
      src/bootsupport/modules/punk/ns-0.1.0.tm
  9. 2
      src/bootsupport/modules/punk/path-0.1.0.tm
  10. 36
      src/bootsupport/modules/shellfilter-0.1.9.tm
  11. 15
      src/bootsupport/modules/textblock-0.1.2.tm
  12. 74
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.1.tm
  13. 36
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  14. 576
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  15. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm
  16. 81
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  17. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  18. 7
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  19. 40
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm
  20. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  21. 36
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm
  22. 15
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.2.tm
  23. 74
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.1.tm
  24. 36
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  25. 576
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  26. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm
  27. 81
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  28. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  29. 7
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  30. 40
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm
  31. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  32. 36
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm
  33. 15
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.2.tm
  34. 74
      src/vfs/_vfscommon.vfs/modules/punk-0.1.tm
  35. 36
      src/vfs/_vfscommon.vfs/modules/punk/aliascore-0.1.0.tm
  36. 576
      src/vfs/_vfscommon.vfs/modules/punk/args-0.1.0.tm
  37. 2
      src/vfs/_vfscommon.vfs/modules/punk/fileline-0.1.0.tm
  38. 81
      src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.1.tm
  39. 2
      src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/module-0.1.0.tm
  40. 7
      src/vfs/_vfscommon.vfs/modules/punk/nav/fs-0.1.0.tm
  41. 40
      src/vfs/_vfscommon.vfs/modules/punk/ns-0.1.0.tm
  42. 2
      src/vfs/_vfscommon.vfs/modules/punk/path-0.1.0.tm
  43. 17
      src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.tm
  44. 1491
      src/vfs/_vfscommon.vfs/modules/punk/safe-0.1.0.tm
  45. 36
      src/vfs/_vfscommon.vfs/modules/shellfilter-0.1.9.tm
  46. 181
      src/vfs/_vfscommon.vfs/modules/termscheme-0.1.0.tm
  47. 15
      src/vfs/_vfscommon.vfs/modules/textblock-0.1.2.tm

74
src/bootsupport/modules/punk-0.1.tm

@ -6976,7 +6976,7 @@ namespace eval punk {
-punctchars -default { [list \{ \} \" \\ - _ + = . > , < ' : \; ` ~ ! @ # \$ % ^ & * \[ \] ( ) | / ?] }
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
# -- --- --- --- --- ---
@ -7560,79 +7560,13 @@ namespace eval punk {
#this hides windows cmd's mode command - probably no big deal - anyone who needs it will know how to exec it.
interp alias {} mode {} punk::mode
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
tailcall punk::lib::aliases $glob
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
tailcall punk::lib::alias $aliasorglob {*}$args
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
#pipeline-toys - put in lib/scriptlib?
##geometric mean
@ -7669,8 +7603,6 @@ namespace eval punk {
interp alias {} help {} punk::help
interp alias {} aliases {} punk::aliases
interp alias {} alias {} punk::alias
interp alias {} treemore {} punk::xmore tree
interp alias {} tmhere {} .= pwd |path> {::tcl::tm::add {*}$data; set path} |> inspect -label added_to_module_path <0/#|

36
src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -105,6 +105,8 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
list_as_lines ::punk::lib::list_as_lines\
lines_as_list ::punk::lib::lines_as_list\
@ -155,34 +157,48 @@ tcl::namespace::eval punk::aliascore {
set opts [dict merge $defaults $args]
set opt_force [dict get $opts -force]
#we never override existing aliases to ::repl::interp* even if -force = 1
#(these are our safebase aliases)
set ignore_pattern "::repl::interp*"
set ignore_aliases [list]
variable aliases
if {!$opt_force} {
set existing [list]
set conflicts [list]
foreach {a cmd} $aliases {
if {[tcl::info::commands ::$a] ne ""} {
lappend existing $a
if {[llength $cmd] > 1} {
#use alias mechanism
set existing_target [interp alias "" $a]
set existing_alias [interp alias "" $a]
if {$existing_alias ne ""} {
set existing_target $existing_alias
if {[string match $ignore_pattern $existing_target]} {
#don't consider it a conflict - will use ignore_aliases to exclude it below
lappend ignore_aliases $a
continue
}
} else {
#using namespace import
#check origin
set existing_target [tcl::namespace::origin $cmd]
if {[catch {tcl::namespace::origin $a} existing_command]} {
set existing_command ""
}
set existing_target $existing_command
}
if {$existing_target ne $cmd} {
#command exists in global ns but doesn't match our defined aliases/imports
lappend conflicts $a
}
}
}
if {[llength $conflicts]} {
if {!$opt_force && [llength $conflicts]} {
error "punk::aliascore::init declined to create any aliases or imports because -force == 0 and conflicts found:$conflicts"
}
}
set tempns ::temp_[info cmdcount] ;#temp ns for renames
dict for {a cmd} $aliases {
#puts "aliascore $a -> $cmd"
if {$a in $ignore_aliases} {
continue
}
if {[llength $cmd] > 1} {
interp alias {} $a {} {*}$cmd
} else {
@ -205,7 +221,7 @@ tcl::namespace::eval punk::aliascore {
}
}
#tcl::namespace::delete $tempns
return [dict keys $aliases]
return [dict create aliases [dict keys $aliases] unchanged $ignore_aliases changed $conflicts]
}

576
src/bootsupport/modules/punk/args-0.1.0.tm

@ -58,7 +58,7 @@
# #setting -type none indicates a flag that doesn't take a value (solo flag)
# -nocomplain -type none
# *values -min 1 -max -1
# } $args]] opts values
# } $args]] leaders opts values
#
# puts "translation is [dict get $opts -translation]"
# foreach f [dict values $values] {
@ -68,7 +68,7 @@
#}]
#[para]The lines beginning with * are optional in most cases and can be used to set defaults and some extra controls
#[para] - the above example would work just fine with only the -<optionname> lines, but would allow zero filenames to be supplied as no -min value is set for *values
#[para]valid * lines being with *proc *opts *values
#[para]valid * lines being with *proc *leaders *opts *values
#[para]lines beginning with a dash define options - a name can optionally be given to each trailing positional argument.
#[para]If no names are defined for positional arguments, they will end up in the values key of the dict with numerical keys starting at zero.
#[para]e.g the result from the punk::args call above may be something like:
@ -299,6 +299,22 @@ tcl::namespace::eval punk::args {
}
return $result
}
proc flatzip {l1 l2} {
concat {*}[lmap a $l1 b $l2 {list $a $b}]
}
if {[info commands lseq] ne ""} {
#tcl 8.7+ lseq significantly faster, especially for larger ranges
#The internal rep can be an 'arithseries' with no string representation
proc zero_to_n {n} {
lseq 0 $n
}
} else {
proc zero_to_n {n} {
lsearch -all [lrepeat $n 0] *
}
}
#todo? -synonym/alias ? (applies to opts only not values)
#e.g -background -aliases {-bg} -default White
@ -309,8 +325,8 @@ tcl::namespace::eval punk::args {
variable argspec_cache
#variable argspecs ;#REVIEW!!
variable argspec_ids
variable initial_optspec_defaults
variable initial_valspec_defaults
#variable initial_optspec_defaults
#variable initial_valspec_defaults
#ideally we would use a fast hash algorithm to produce a short key with low collision probability.
#something like md5 would be ok (this is non cryptographic) - but md5 uses open and isn't usable by default in a safe interp.
#review - check if there is a built-into-tcl way to do this quickly
@ -335,6 +351,19 @@ tcl::namespace::eval punk::args {
-regexprepass {}\
-validationtransform {}\
]
set leaderspec_defaults [tcl::dict::create\
-type string\
-optional 0\
-allow_ansi 1\
-validate_ansistripped 0\
-strip_ansi 0\
-nocase 0\
-choiceprefix 1\
-choicerestricted 1\
-multiple 0\
-regexprepass {}\
-validationtransform {}\
]
set valspec_defaults [tcl::dict::create\
-type string\
-optional 0\
@ -357,15 +386,20 @@ tcl::namespace::eval punk::args {
#default -allow_ansi to 1 and -validate_ansistripped to 0 and -strip_ansi 0 - it takes time to strip ansi
#todo - detect if anything in the spec uses -allow_ansi 0, -validate_ansistripped 1 or -strip_ansi 1 and set a flag indicating if punk::ansi::ta::detect should be run on the argslist
set leader_required [list]
set opt_required [list]
set val_required [list]
set arg_info [tcl::dict::create]
set arg_checks [tcl::dict::create]
set opt_defaults [tcl::dict::create]
set opt_names [list] ;#defined opts
set leader_defaults [tcl::dict::create]
set val_defaults [tcl::dict::create]
set opt_solos [list]
#first process dashed and non-dashed argspecs without regard to whether non-dashed are at the beginning or end
set leader_names [list]
set val_names [list]
set records [list]
@ -439,10 +473,14 @@ tcl::namespace::eval punk::args {
}
set proc_info {}
set id_info {} ;#e.g -children <list> ??
set leader_min 0
set leader_max 0 ;#default 0 but if leader_min is set switches to -1 for no limit
#(common case of no leaders specified)
set opt_any 0
set val_min 0
set val_max -1 ;#-1 for no limit
set spec_id ""
set argspace "leaders" ;#leaders -> options -> values
foreach ln $records {
set trimln [tcl::string::trim $ln]
switch -- [tcl::string::index $trimln 0] {
@ -477,6 +515,10 @@ tcl::namespace::eval punk::args {
set proc_info $starspecs
}
opts {
if {$argspace eq "values"} {
error "punk::args::definition - *opts declaration must come before *values - received '$linespecs'"
}
set argspace "options"
foreach {k v} $starspecs {
switch -- $k {
-any -
@ -537,7 +579,86 @@ tcl::namespace::eval punk::args {
}
}
}
leaders {
if {$argspace in [list options values]} {
error "punk::args::definition - *leaders declaration must come before all options and values"
}
foreach {k v} $starspecs {
switch -- $k {
-min -
-minvalues {
if {$v < 0} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
}
set leader_min $v
if {$leader_max == 0} {
set leader_max -1
}
}
-max -
-maxvalues {
if {$v < -1} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
}
set leader_max $v
}
-minlen - -maxlen - -range - -choices - -choicelabels - -choiceprefix - -choicerestricted - -nocase {
#review - only apply to certain types?
tcl::dict::set leaderspec_defaults $k $v
}
-nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels {
if {$v} {
tcl::dict::unset leaderspec_defaults $k
}
}
-type {
switch -- $v {
int - integer {
set v int
}
char - character {
set v char
}
bool - boolean {
set v bool
}
dict - dictionary {
set v dict
}
list {
}
default {
#todo - disallow unknown types unless prefixed with custom-
}
}
tcl::dict::set leaderspec_defaults $k $v
}
-optional -
-allow_ansi -
-validate_ansistripped -
-strip_ansi -
-regexprepass -
-regexprefail -
-validationtransform -
-multiple {
tcl::dict::set leaderspec_defaults $k $v
}
default {
set known { -min -minvalues -max -maxvalues\
-minlen -maxlen -range -choices -choicelabels -choiceprefix -choicerestricted -nocase\
-nominlen -nomaxlen -norange -nochoices -nochoicelabels\
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\
}
error "punk::args::definition - unrecognised key '$k' in *leaders line. Known keys: $known"
}
}
}
}
values {
set argspace "values"
foreach {k v} $starspecs {
switch -- $k {
-min -
@ -610,11 +731,16 @@ tcl::namespace::eval punk::args {
}
default {
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *leaders *opts or *values - use **name if paramname needs to be *name"
}
}
continue
} elseif {$firstchar eq "-"} {
if {$argspace eq "leaders"} {
set argspace "options"
} elseif {$argspace eq "values"} {
error "punk::args::definition - invalid placement of line '$ln' - must come before *values"
}
set argspecs $linespecs
tcl::dict::set argspecs -ARGTYPE option
lappend opt_names $argname
@ -625,8 +751,16 @@ tcl::namespace::eval punk::args {
set argname [tcl::string::range $argname 1 end]
}
set argspecs $linespecs
if {$argspace eq "leaders"} {
tcl::dict::set argspecs -ARGTYPE leader
lappend leader_names $argname
if {$leader_max == 0} {
set leader_max [llength $leader_names]
}
} else {
tcl::dict::set argspecs -ARGTYPE value
lappend val_names $argname
}
set is_opt 0
}
#assert - we only get here if it is a value or flag specification line.
@ -634,7 +768,11 @@ tcl::namespace::eval punk::args {
if {$is_opt} {
set spec_merged $optspec_defaults
} else {
if {$argspace eq "values"} {
set spec_merged $valspec_defaults
} else {
set spec_merged $leaderspec_defaults
}
}
foreach {spec specval} $argspecs {
#literal-key switch - bytecompiled to jumpTable
@ -726,23 +864,37 @@ tcl::namespace::eval punk::args {
if {![tcl::dict::get $argspecs -optional] && ![tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
lappend opt_required $argname
} else {
if {$argspace eq "leaders"} {
lappend leader_required $argname
} else {
lappend val_required $argname
}
}
}
if {[tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
tcl::dict::set opt_defaults $argname [tcl::dict::get $argspecs -default]
} else {
if {$argspace eq "leaders"} {
tcl::dict::set leader_defaults $argname [tcl::dict::get $argspecs -default]
} else {
tcl::dict::set val_defaults $argname [tcl::dict::get $argspecs -default]
}
}
}
}
# REVIEW
foreach leadername [lrange $leader_names 0 end] {
if {[tcl::dict::get $arg_info $leadername -multiple]} {
error "bad key -multiple on argument spec for leader '$valname'. Only the last value argument specification can be marked -multiple"
}
}
#confirm any valnames before last don't have -multiple key
foreach valname [lrange $val_names 0 end-1] {
if {[tcl::dict::get $arg_info $valname -multiple]} {
error "bad key -multiple on argument spec for '$valname'. Only the last value argument specification can be marked -multiple"
error "bad key -multiple on argument spec for value '$valname'. Only the last value argument specification can be marked -multiple"
}
}
if {$spec_id eq "" || [tcl::string::tolower $spec_id] eq "auto"} {
@ -750,7 +902,11 @@ tcl::namespace::eval punk::args {
set spec_id "autoid_[incr id_counter]"
}
#todo - document that ambiguities in API are likely if both *leaders and *values used
#todo - do some checks for obvious bad definitions involving a mix of *leaders and *values (e.g with optional options)
set leader_checks_defaults [tcl::dict::remove $leaderspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set opt_checks_defaults [tcl::dict::remove $optspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set val_checks_defaults [tcl::dict::remove $valspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
@ -758,6 +914,13 @@ tcl::namespace::eval punk::args {
id $spec_id\
arg_info $arg_info\
arg_checks $arg_checks\
leader_defaults $leader_defaults\
leader_required $leader_required\
leader_names $leader_names\
leader_min $leader_min\
leader_max $leader_max\
leaderspec_defaults $leaderspec_defaults\
leader_checks_defaults $leader_checks_defaults\
opt_defaults $opt_defaults\
opt_required $opt_required\
opt_names $opt_names\
@ -782,13 +945,33 @@ tcl::namespace::eval punk::args {
return $result
}
proc get_spec {id} {
proc get_spec {id {patternlist *}} {
variable argspec_ids
if {[tcl::dict::exists $argspec_ids $id]} {
if {$patternlist eq "*"} {
return [tcl::dict::get $argspec_ids $id]
} else {
set spec [tcl::dict::get $argspec_ids $id]
set result ""
set specdict [definition $spec]
set arg_info [dict get $specdict arg_info]
foreach pat $patternlist {
set matches [dict keys $arg_info $pat]
foreach m $matches {
set def [dict get $arg_info $m]
set def [dict remove $def -ARGTYPE]
append result \n "$m $def"
}
}
return $result
}
}
return
}
#proc get_spec_leaders ??
#proc get_spec_opts ??
#proc get_spec_values ??
proc get_spec_ids {{match *}} {
variable argspec_ids
return [tcl::dict::keys $argspec_ids $match]
@ -876,17 +1059,17 @@ tcl::namespace::eval punk::args {
$t add_column -headers $blank_header_col
$t add_column -headers $blank_header_col
if {"$procname$prochelp" eq ""} {
$t configure_header 0 -values {Arg Type Default Multiple Help}
$t configure_header 0 -values {Arg Type Default Multi Help}
} elseif {$procname eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} elseif {$prochelp eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} else {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 2 -values {Arg Type Default Multiple Help}
$t configure_header 2 -values {Arg Type Default Multi Help}
}
@ -894,7 +1077,8 @@ tcl::namespace::eval punk::args {
#set A_DEFAULT [a+ brightwhite Brightgreen]
set A_DEFAULT ""
set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a]
set greencheck [a+ brightgreen]\u2713[a] ;#green tick
set soloflag [a+ brightcyan]\u2690[a] ;#flag - may be replacement char in old dos prompt (?)
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
@ -930,20 +1114,21 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names
}
}
set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names [dict get $spec_dict leader_names]
set trailing_val_names [dict get $spec_dict val_names]
#dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
# if {![string match -* $argname]} {
# lappend leading_val_names [lpop trailing_val_names 0]
# } else {
# break
# }
#}
#if {![llength $leading_val_names] && ![llength $opt_names]} {
# #all vals were actually trailing - no opts
# set trailing_val_names $leading_val_names
# set leading_val_names {}
#}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
@ -954,7 +1139,7 @@ tcl::namespace::eval punk::args {
foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} {
set default $A_DEFAULT[dict get $arginfo -default]$RST
set default "'$A_DEFAULT[dict get $arginfo -default]$RST'"
} else {
set default ""
}
@ -1022,7 +1207,14 @@ tcl::namespace::eval punk::args {
} else {
set multiple ""
}
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {[::punk::args::Dict_getdef $arginfo -optional 0] == 1 || [dict exists $arginfo -default]} {
set argshow "?${argshow}?"
}
set typeshow [dict get $arginfo -type]
if {$typeshow eq "none"} {
set typeshow "$typeshow $soloflag"
}
$t add_row [list $argshow $typeshow $default $multiple $help]
if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
}
@ -1087,7 +1279,7 @@ tcl::namespace::eval punk::args {
#[para]argumentname -key val -ky2 val2...
#[para]where the valid keys for each option specification are: -default -type -range -choices
#[para]comment lines begining with # are ignored and can be placed anywhere except within a multiline value where it would become part of that value
#[para]lines beginning with *proc *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]lines beginning with *proc *leaders *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]*opts or *values lines can appear multiple times with defaults affecting flags/values that follow.
#[arg_def list rawargs]
#[para] This is a list of the arguments to parse. Usually it will be the $args value from the containing proc,
@ -1162,17 +1354,55 @@ tcl::namespace::eval punk::args {
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
#dict for {a info} $arg_info {
# #todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
# if {![string match -* $a]} {
# #lappend pre_values [lpop rawargs 0]
# if {[catch {lpop rawargs 0} val]} {
# break
# } else {
# lappend pre_values $val
# }
# } else {
# break
# }
#}
set argnames [dict keys $arg_info]
set optnames [lsearch -all -inline $argnames -*]
set ridx 0
set rawargs_copy $rawargs
if {$leader_max != 0} {
foreach r $rawargs_copy {
if {$leader_max != -1 && $ridx > $leader_max-1} {
break
}
if {[string match -* $r]} {
if {$r eq "--"} {
break
}
set matchopt [::tcl::prefix::match -error {} $optnames $r]
if {$matchopt ne ""} {
#flaglike matches a known flag - don't treat as leader
break
}
if {![string match -* [lindex $argnames $ridx]]} {
#there is a named leading positional for this position
lappend pre_values [lpop rawargs 0]
incr ridx
continue
} else {
break
}
}
lappend pre_values [lpop rawargs 0]
incr ridx
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} {
set leaders [list]
set arglist {}
set post_values {}
#val_min, val_max
@ -1326,199 +1556,40 @@ tcl::namespace::eval punk::args {
break
}
}
set values [list {*}$pre_values {*}$post_values]
#set values [list {*}$pre_values {*}$post_values]
set leaders $pre_values
set values $post_values
} else {
set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set leaders $pre_values
set values $rawargs
#set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list]
}
#puts stderr "--> arglist: $arglist"
#puts stderr "--> values: $values"
}
if {$id eq "jtest"} {
if {[set eopts [lsearch -exact $rawargs "--"]] >= 0} {
lappend flagsreceived --
set values [lrange $rawargs $eopts+1 end]
set arglist [lrange $rawargs 0 $eopts-1]
set maxidx [expr {[llength $arglist]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $arglist $i]
if {![tcl::string::match -* $a]} {
#we can't treat as first positional arg - as it comes before the eopt indicator --
arg_error "punk::args::get_dict bad options for [Get_caller]. Expected flag (leading -) at position $i got:$rawargs" $argspecs
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $arglist $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $arglist $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
} else {
tcl::dict::set opts $a $newval
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
} else {
if {[lsearch $rawargs -*] >= 0} {
#no -- end of opts indicator
#to support option values with leading dash e.g -offset -1 , we can't just use the last flagindex to determine start of positional args.
#we break on first non-flag looking argument that isn't in an option's value position and use that index as the division.
#The caller should use -- if the first positional arg is likely or has the potential to start with a dash.
set maxidx [expr {[llength $rawargs]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $rawargs $i]
#we can automatically rule out arguments containing whitespace from the set of simple flags beginning with a dash
#This helps for example when first value is a dict or list in which the first item happens to begin with a dash
#explicit -- still safer in many cases, but this is a reasonable and fast enough test
if {![tcl::string::match -* $a] || [regexp {\s+} $a]} {
#assume beginning of positional args
incr i -1
set positionalidx 0 ;#index for unnamed positionals (both leaders and values)
set ldridx 0
set leadernames_received [list]
set leaders_dict $leader_defaults
set num_leaders [llength $leaders]
foreach leadername $leader_names ldr $leaders {
if {$ldridx+1 > $num_leaders} {
break
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $rawargs $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $rawargs $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
if {$leadername ne ""} {
tcl::dict::set leaders_dict $leadername $ldr
lappend leadernames_received $leadername
} else {
tcl::dict::set opts $a $newval
tcl::dict::set leaders_dict $positionalidx $ldr
tcl::dict::set arg_info $positionalidx $leaderspec_defaults
tcl::dict::set arg_checks $positionalidx $leader_checks_defaults
lappend leadernames_received $positionalidx
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
incr ldridx
incr positionalidx
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end]
#puts "$i--->arglist:$arglist"
#puts "$i--->values:$values"
} else {
set values $rawargs ;#no -flags detected
set arglist [list]
}
}
}
set validx 0
set in_multiple ""
@ -1547,13 +1618,29 @@ tcl::namespace::eval punk::args {
tcl::dict::lappend values_dict $in_multiple $val
#name already seen
} else {
tcl::dict::set values_dict $validx $val
tcl::dict::set arg_info $validx $valspec_defaults
tcl::dict::set arg_checks $validx $val_checks_defaults
lappend valnames_received $validx
tcl::dict::set values_dict $positionalidx $val
tcl::dict::set arg_info $positionalidx $valspec_defaults
tcl::dict::set arg_checks $positionalidx $val_checks_defaults
lappend valnames_received $positionalidx
}
}
incr validx
incr positionalidx
}
if {$leader_max == -1} {
#only check min
if {$num_leaders < $leader_min} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected at least $leader_min" $argspecs
}
} else {
if {$num_leaders < $leader_min || $num_leaders > $leader_max} {
if {$leader_min == $leader_max} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected exactly $leader_min" $argspecs
} else {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected between $leader_min and $leader_max inclusive" $argspecs
}
}
}
if {$val_max == -1} {
@ -1592,6 +1679,9 @@ tcl::namespace::eval punk::args {
# error "Required value missing for [Get_caller]. missing values $missing marked with -optional false - so must be present"
#}
#for now (2024-06) punk::lib::ldiff is a better compromise across normal/safe interps e.g 0.7/0.8us
if {[llength [set missing [punklib_ldiff $leader_required $leadernames_received]]]} {
arg_error "Required leader missing for [Get_caller]. missing values: '$missing' marked with -optional false - so must be present" $argspecs
}
if {[llength [set missing [punklib_ldiff $opt_required $flagsreceived]]]} {
arg_error "Required option missing for [Get_caller]. missing flags: '$missing' are marked with -optional false - so must be present in full-length form" $argspecs
}
@ -1605,7 +1695,7 @@ tcl::namespace::eval punk::args {
#todo - allow defaults outside of choices/ranges
#check types,ranges,choices
set opts_and_values [tcl::dict::merge $opts $values_dict]
set opts_and_values [tcl::dict::merge $leaders_dict $opts $values_dict]
#set combined_defaults [tcl::dict::merge $val_defaults $opt_defaults] ;#can be no key collisions - we don't allow a value key beginning with dash - opt names must begin with dash
#puts "---opts_and_values:$opts_and_values"
#puts "---arg_info:$arg_info"
@ -1657,11 +1747,17 @@ tcl::namespace::eval punk::args {
set choiceprefix [tcl::dict::get $thisarg -choiceprefix]
set choicerestricted [tcl::dict::get $thisarg -choicerestricted]
set nocase [tcl::dict::get $thisarg -nocase]
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
set dname leaders_dict
}
option {
set dname opts
} else {
}
value {
set dname values_dict
}
}
set idx 0 ;#
#opts/values_dict $argname membmer has been populated with the actual entered choices - which might be prefixes
#assert llength $vlist == llength [dict get $dname $argname]
@ -1819,8 +1915,8 @@ tcl::namespace::eval punk::args {
lappend pass_quick_list_e_check $e_check
}
}
set remaining_e [punk::lib::ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punk::lib::ldiff $vlist_check $pass_quick_list_e_check]
set remaining_e [punklib_ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punklib_ldiff $vlist_check $pass_quick_list_e_check]
}
if {$regexprefail ne ""} {
foreach e $remaining_e e_check $remaining_e_check {
@ -2036,23 +2132,37 @@ tcl::namespace::eval punk::args {
if {$is_strip_ansi} {
set stripped_list [lmap e $vlist_original {punk::ansi::ansistrip $e}] ;#no faster or slower, but more concise than foreach
if {[tcl::dict::get $thisarg -multiple]} {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict $argname $stripped_list
}
option {
tcl::dict::set opts $argname $stripped_list
} else {
}
value {
tcl::dict::set values_dict $argname $stripped_list
}
}
} else {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict [lindex $stripped_list 0]
}
option {
tcl::dict::set opts $argname [lindex $stripped_list 0]
} else {
}
value {
tcl::dict::set values_dict [lindex $stripped_list 0]
}
}
}
}
}
#maintain order of opts $opts values $values as caller may use lassign.
return [tcl::dict::create opts $opts values $values_dict]
set receivednames [list {*}$leadernames_received {*}$flagsreceived {*}$valnames_received]
set received_posns [concat {*}[lmap a $receivednames b [zero_to_n [expr {[llength $receivednames]-1}]] {list $a $b}]] ;#flat zip named with overall posn including opts
return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns]
}
#proc sample1 {p1 args} {
@ -2063,6 +2173,16 @@ tcl::namespace::eval punk::args {
#}
punk::args::definition {
*id punk::args::TEST
*opts -optional 0
-o1 -default 111 -help "opt 1 mandatory"
*opts -optional 1
-o2 -default 222 -help "opt 2 optional"
*values -min 0 -max 1
v -help\
"v1 optional"
}
#*** !doctools

2
src/bootsupport/modules/punk/fileline-0.1.0.tm

@ -1556,7 +1556,7 @@ namespace eval punk::fileline::lib {
set argd [punk::args::get_dict {
-offset -default 0
} $args]
lassign [dict values $argd] opts remainingargs
lassign [dict values $argd] leaders opts remainingargs
}

81
src/bootsupport/modules/punk/lib-0.1.1.tm

@ -211,6 +211,9 @@ tcl::namespace::eval punk::lib::compat {
#*** !doctools
#[list_begin definitions]
if {"::lremove" ne [info commands ::lremove]} {
#puts stderr "Warning - no built-in lremove"
interp alias {} lremove {} ::punk::lib::compat::lremove
@ -393,6 +396,80 @@ namespace eval punk::lib {
set has_twapi [expr {![catch {package require twapi}]}]
}
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::lib::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::lib::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
# == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
# Maintenance - This is the primary source for tm_version... functions
# - certain packages script require these but without package dependency
@ -2894,7 +2971,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
-joinchar -default \n
*values -min 1 -max 1
} $args]] opts values
} $args]] leaders opts values
puts "opts:$opts"
puts "values:$values"
return [join [tcl::dict::get $values 0] [tcl::dict::get $opts -joinchar]]
@ -2936,7 +3013,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
*opts -any 1
-block -default {}
} $args]] opts valuedict
} $args]] leaderdict opts valuedict
tailcall linelist {*}$opts {*}[tcl::dict::values $valuedict]
}

2
src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm

@ -153,7 +153,7 @@ namespace eval punk::mix::commandset::module {
module -type string
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set module [dict get $values module]
#set opts [dict merge $defaults $args]

7
src/bootsupport/modules/punk/nav/fs-0.1.0.tm

@ -643,7 +643,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values_dict
lassign [dict values $argd] leaders opts values_dict
set opt_stripbase [dict get $opts -stripbase]
set opt_formatsizes [dict get $opts -formatsizes]
@ -726,6 +726,7 @@ tcl::namespace::eval punk::nav::fs {
# -searchbase is always passed through - and is only used to construct a location path if a relative searchspec was supplied
proc dirfiles_dict {args} {
set argspecs {
*id punk::nav::fs::dirfiles_dict
*opts -any 0
-searchbase -default ""
-tailglob -default "\uFFFF"
@ -735,7 +736,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1 -type string
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
#puts stderr "searchspecs: $searchspecs [llength $searchspecs]"
@ -1000,7 +1001,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 1 -max -1 -type dict
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set list_of_dicts [dict values $vals]

40
src/bootsupport/modules/punk/ns-0.1.0.tm

@ -20,8 +20,8 @@
package require punk::lib
package require punk::args
tcl::namespace::eval ::punk_dynamic::ns {
tcl::namespace::eval ::punk::ns::evaluator {
#eval-_NS_xxx_NS_etc procs
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -29,6 +29,12 @@ tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nsimport_noclobber corp
catch {
package require debug
debug define punk.ns.compile
#debug on punk.ns.compile
#debug level punk.ns.compile 3
}
#leading colon makes it hard (impossible?) to call directly if not within the namespace
proc ns/ {v {ns_or_glob ""} args} {
@ -200,16 +206,17 @@ tcl::namespace::eval punk::ns {
return $body
}
proc nseval {fqns script} {
#create one proc for each fully qualified namespace to evaluate script
if {![string match ::* $fqns]} {
error "nseval only accepts a fully qualified namespace"
}
set loc [string map {:: _sep_} $fqns]
set loc [string map {:: _NS_} $fqns]
#set cmd ::punk::pipecmds::nseval_$loc
set cmd ::punk_dynamic::ns::eval-$loc
set cmd ::punk::ns::evaluator::eval-$loc
if {$cmd ni [info commands $cmd]} {
append body \n [nseval_script $fqns]
proc $cmd {script} $body
debug.punk.pipe.compile {proc $cmd} 6
debug.punk.ns.compile {proc $cmd} 2
}
tailcall $cmd $script
}
@ -800,14 +807,23 @@ tcl::namespace::eval punk::ns {
set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
#set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set lenlist1 [lmap v [list {*}$children1 ""] {string length $v}]
set chwidest1 [tcl::mathfunc::max {*}$lenlist1]
#set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$children2 ""] {string length $v}]]
#wrap the cmd in [list] (just for the width calc) to get a proper length for what will actually be displayed
set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
#set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest1 [tcl::mathfunc::max {*}[lmap v [list {*}$elements1 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$elements2 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [tcl::mathfunc::max {*}[lmap v [list {*}$elements3 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [tcl::mathfunc::max {*}[lmap v [list {*}$elements4 ""] {string length [list [lindex $v 1]]}]]
set displaylist [list]
set col1 [string repeat " " [expr {$chwidest1 + 8}]]
@ -1875,7 +1891,7 @@ tcl::namespace::eval punk::ns {
*values -min 1 -max 1
sourcepattern -type string -optional 0
}
lassign [dict values [punk::args::get_dict $argspecs $args]] opts values
lassign [dict values [punk::args::get_dict $argspecs $args]] leaders opts values
set sourcepattern [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]

2
src/bootsupport/modules/punk/path-0.1.0.tm

@ -662,7 +662,7 @@ namespace eval punk::path {
*values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1
} $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set tailglobs [dict values $values]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]

36
src/bootsupport/modules/shellfilter-0.1.9.tm

@ -13,12 +13,24 @@
tcl::namespace::eval shellfilter::log {
variable allow_adhoc_tags 1
variable open_logs [tcl::dict::create]
variable is_enabled 0
proc disable {} {
variable is_enabled
set is_enabled 0
proc ::shellfilter::log::open {tag settingsdict} {}
proc ::shellfilter::log::write {tag msg} {}
proc ::shellfilter::log::write_sync {tag msg} {}
proc ::shellfilter::log::close {tag} {}
}
proc enable {} {
variable is_enabled
set is_enabled 1
#'tag' is an identifier for the log source.
# each tag will use it's own thread to write to the configured log target
proc open {tag {settingsdict {}}} {
proc ::shellfilter::log::open {tag {settingsdict {}}} {
upvar ::shellfilter::sources sourcelist
package require shellthread
if {![dict exists $settingsdict -tag]} {
tcl::dict::set settingsdict -tag $tag
} else {
@ -36,7 +48,7 @@ tcl::namespace::eval shellfilter::log {
#puts stderr "shellfilter::log::open this_threadid: [thread::id] tag: $tag worker_tid: $worker_tid"
return $worker_tid
}
proc write {tag msg} {
proc ::shellfilter::log::write {tag msg} {
upvar ::shellfilter::sources sourcelist
variable allow_adhoc_tags
if {!$allow_adhoc_tags} {
@ -47,14 +59,16 @@ tcl::namespace::eval shellfilter::log {
shellthread::manager::write_log $tag $msg
}
#write_sync - synchronous processing with logging thread, slower but potentially useful for debugging/testing or forcing delay til log written
proc write_sync {tag msg} {
proc ::shellfilter::log::write_sync {tag msg} {
shellthread::manager::write_log $tag $msg -async 0
}
proc close {tag} {
proc ::shellfilter::log::close {tag} {
#shellthread::manager::close_worker $tag
shellthread::manager::unsubscribe [list $tag]; #workertid will be added back to free list if no tags remain subscribed
}
}
#review
#configure whether we can call shellfilter::log::write without having called open first
proc require_open {{is_open_required {}}} {
@ -73,6 +87,12 @@ tcl::namespace::eval shellfilter::log {
}
}
}
if {[catch {package require shellthread}]} {
shellfilter::log::disable
} else {
shellfilter::log::enable
}
}
namespace eval shellfilter::pipe {
#write channel for program. workerthread reads other end of fifo2 and writes data somewhere
@ -1594,7 +1614,13 @@ namespace eval shellfilter::stack {
set tag "SHELLFILTER::STACK"
#JMN - load from config
#::shellfilter::log::open $tag {-syslog 127.0.0.1:514}
if {[catch {
::shellfilter::log::open $tag {-syslog ""}
} err]} {
#e.g safebase interp can't load required modules such as shellthread (or Thread)
puts stderr "shellfilter::show_pipeline cannot open log"
return
}
::shellfilter::log::write $tag "transform stack for $pipename $args"
foreach tf $stack {
::shellfilter::log::write $tag " $tf"

15
src/bootsupport/modules/textblock-0.1.2.tm

@ -62,7 +62,14 @@ catch {package require patternpunk}
package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
if {[catch {
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
} errM]} {
#catch this too in case stderr not available
catch {
puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
}
}
package require textutil
@ -5182,7 +5189,7 @@ tcl::namespace::eval textblock {
lassign [punk::args::get_dict {
-ansiresets -default 1 -type integer
blocks -type string -multiple 1
} $args] _o opts _v values
} $args] _l leaders _o opts _v values
set blocks [tcl::dict::get $values blocks]
set idx 0
@ -5844,7 +5851,7 @@ tcl::namespace::eval textblock {
*values -min 1
frametype -choices "<ftlist>" -choiceprefix 0 -choicerestricted 0 -type dict\
-help "name from the predefined frametypes or an adhoc dictionary."
memberglob -type globstring -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
memberglob -type globstring -optional 1 -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
corner noncorner top bottom vertical horizontal left right
hl hlt hlb vsl vll vlr tlc trc blc brc hltj hlbj vllj vlrj
}\
@ -8110,7 +8117,7 @@ tcl::namespace::eval textblock {
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
*values -min 0 -max 1
size -default 1 -type integer
}
proc gcross {args} {

74
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.1.tm

@ -6976,7 +6976,7 @@ namespace eval punk {
-punctchars -default { [list \{ \} \" \\ - _ + = . > , < ' : \; ` ~ ! @ # \$ % ^ & * \[ \] ( ) | / ?] }
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
# -- --- --- --- --- ---
@ -7560,79 +7560,13 @@ namespace eval punk {
#this hides windows cmd's mode command - probably no big deal - anyone who needs it will know how to exec it.
interp alias {} mode {} punk::mode
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
tailcall punk::lib::aliases $glob
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
tailcall punk::lib::alias $aliasorglob {*}$args
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
#pipeline-toys - put in lib/scriptlib?
##geometric mean
@ -7669,8 +7603,6 @@ namespace eval punk {
interp alias {} help {} punk::help
interp alias {} aliases {} punk::aliases
interp alias {} alias {} punk::alias
interp alias {} treemore {} punk::xmore tree
interp alias {} tmhere {} .= pwd |path> {::tcl::tm::add {*}$data; set path} |> inspect -label added_to_module_path <0/#|

36
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -105,6 +105,8 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
list_as_lines ::punk::lib::list_as_lines\
lines_as_list ::punk::lib::lines_as_list\
@ -155,34 +157,48 @@ tcl::namespace::eval punk::aliascore {
set opts [dict merge $defaults $args]
set opt_force [dict get $opts -force]
#we never override existing aliases to ::repl::interp* even if -force = 1
#(these are our safebase aliases)
set ignore_pattern "::repl::interp*"
set ignore_aliases [list]
variable aliases
if {!$opt_force} {
set existing [list]
set conflicts [list]
foreach {a cmd} $aliases {
if {[tcl::info::commands ::$a] ne ""} {
lappend existing $a
if {[llength $cmd] > 1} {
#use alias mechanism
set existing_target [interp alias "" $a]
set existing_alias [interp alias "" $a]
if {$existing_alias ne ""} {
set existing_target $existing_alias
if {[string match $ignore_pattern $existing_target]} {
#don't consider it a conflict - will use ignore_aliases to exclude it below
lappend ignore_aliases $a
continue
}
} else {
#using namespace import
#check origin
set existing_target [tcl::namespace::origin $cmd]
if {[catch {tcl::namespace::origin $a} existing_command]} {
set existing_command ""
}
set existing_target $existing_command
}
if {$existing_target ne $cmd} {
#command exists in global ns but doesn't match our defined aliases/imports
lappend conflicts $a
}
}
}
if {[llength $conflicts]} {
if {!$opt_force && [llength $conflicts]} {
error "punk::aliascore::init declined to create any aliases or imports because -force == 0 and conflicts found:$conflicts"
}
}
set tempns ::temp_[info cmdcount] ;#temp ns for renames
dict for {a cmd} $aliases {
#puts "aliascore $a -> $cmd"
if {$a in $ignore_aliases} {
continue
}
if {[llength $cmd] > 1} {
interp alias {} $a {} {*}$cmd
} else {
@ -205,7 +221,7 @@ tcl::namespace::eval punk::aliascore {
}
}
#tcl::namespace::delete $tempns
return [dict keys $aliases]
return [dict create aliases [dict keys $aliases] unchanged $ignore_aliases changed $conflicts]
}

576
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm

@ -58,7 +58,7 @@
# #setting -type none indicates a flag that doesn't take a value (solo flag)
# -nocomplain -type none
# *values -min 1 -max -1
# } $args]] opts values
# } $args]] leaders opts values
#
# puts "translation is [dict get $opts -translation]"
# foreach f [dict values $values] {
@ -68,7 +68,7 @@
#}]
#[para]The lines beginning with * are optional in most cases and can be used to set defaults and some extra controls
#[para] - the above example would work just fine with only the -<optionname> lines, but would allow zero filenames to be supplied as no -min value is set for *values
#[para]valid * lines being with *proc *opts *values
#[para]valid * lines being with *proc *leaders *opts *values
#[para]lines beginning with a dash define options - a name can optionally be given to each trailing positional argument.
#[para]If no names are defined for positional arguments, they will end up in the values key of the dict with numerical keys starting at zero.
#[para]e.g the result from the punk::args call above may be something like:
@ -299,6 +299,22 @@ tcl::namespace::eval punk::args {
}
return $result
}
proc flatzip {l1 l2} {
concat {*}[lmap a $l1 b $l2 {list $a $b}]
}
if {[info commands lseq] ne ""} {
#tcl 8.7+ lseq significantly faster, especially for larger ranges
#The internal rep can be an 'arithseries' with no string representation
proc zero_to_n {n} {
lseq 0 $n
}
} else {
proc zero_to_n {n} {
lsearch -all [lrepeat $n 0] *
}
}
#todo? -synonym/alias ? (applies to opts only not values)
#e.g -background -aliases {-bg} -default White
@ -309,8 +325,8 @@ tcl::namespace::eval punk::args {
variable argspec_cache
#variable argspecs ;#REVIEW!!
variable argspec_ids
variable initial_optspec_defaults
variable initial_valspec_defaults
#variable initial_optspec_defaults
#variable initial_valspec_defaults
#ideally we would use a fast hash algorithm to produce a short key with low collision probability.
#something like md5 would be ok (this is non cryptographic) - but md5 uses open and isn't usable by default in a safe interp.
#review - check if there is a built-into-tcl way to do this quickly
@ -335,6 +351,19 @@ tcl::namespace::eval punk::args {
-regexprepass {}\
-validationtransform {}\
]
set leaderspec_defaults [tcl::dict::create\
-type string\
-optional 0\
-allow_ansi 1\
-validate_ansistripped 0\
-strip_ansi 0\
-nocase 0\
-choiceprefix 1\
-choicerestricted 1\
-multiple 0\
-regexprepass {}\
-validationtransform {}\
]
set valspec_defaults [tcl::dict::create\
-type string\
-optional 0\
@ -357,15 +386,20 @@ tcl::namespace::eval punk::args {
#default -allow_ansi to 1 and -validate_ansistripped to 0 and -strip_ansi 0 - it takes time to strip ansi
#todo - detect if anything in the spec uses -allow_ansi 0, -validate_ansistripped 1 or -strip_ansi 1 and set a flag indicating if punk::ansi::ta::detect should be run on the argslist
set leader_required [list]
set opt_required [list]
set val_required [list]
set arg_info [tcl::dict::create]
set arg_checks [tcl::dict::create]
set opt_defaults [tcl::dict::create]
set opt_names [list] ;#defined opts
set leader_defaults [tcl::dict::create]
set val_defaults [tcl::dict::create]
set opt_solos [list]
#first process dashed and non-dashed argspecs without regard to whether non-dashed are at the beginning or end
set leader_names [list]
set val_names [list]
set records [list]
@ -439,10 +473,14 @@ tcl::namespace::eval punk::args {
}
set proc_info {}
set id_info {} ;#e.g -children <list> ??
set leader_min 0
set leader_max 0 ;#default 0 but if leader_min is set switches to -1 for no limit
#(common case of no leaders specified)
set opt_any 0
set val_min 0
set val_max -1 ;#-1 for no limit
set spec_id ""
set argspace "leaders" ;#leaders -> options -> values
foreach ln $records {
set trimln [tcl::string::trim $ln]
switch -- [tcl::string::index $trimln 0] {
@ -477,6 +515,10 @@ tcl::namespace::eval punk::args {
set proc_info $starspecs
}
opts {
if {$argspace eq "values"} {
error "punk::args::definition - *opts declaration must come before *values - received '$linespecs'"
}
set argspace "options"
foreach {k v} $starspecs {
switch -- $k {
-any -
@ -537,7 +579,86 @@ tcl::namespace::eval punk::args {
}
}
}
leaders {
if {$argspace in [list options values]} {
error "punk::args::definition - *leaders declaration must come before all options and values"
}
foreach {k v} $starspecs {
switch -- $k {
-min -
-minvalues {
if {$v < 0} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
}
set leader_min $v
if {$leader_max == 0} {
set leader_max -1
}
}
-max -
-maxvalues {
if {$v < -1} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
}
set leader_max $v
}
-minlen - -maxlen - -range - -choices - -choicelabels - -choiceprefix - -choicerestricted - -nocase {
#review - only apply to certain types?
tcl::dict::set leaderspec_defaults $k $v
}
-nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels {
if {$v} {
tcl::dict::unset leaderspec_defaults $k
}
}
-type {
switch -- $v {
int - integer {
set v int
}
char - character {
set v char
}
bool - boolean {
set v bool
}
dict - dictionary {
set v dict
}
list {
}
default {
#todo - disallow unknown types unless prefixed with custom-
}
}
tcl::dict::set leaderspec_defaults $k $v
}
-optional -
-allow_ansi -
-validate_ansistripped -
-strip_ansi -
-regexprepass -
-regexprefail -
-validationtransform -
-multiple {
tcl::dict::set leaderspec_defaults $k $v
}
default {
set known { -min -minvalues -max -maxvalues\
-minlen -maxlen -range -choices -choicelabels -choiceprefix -choicerestricted -nocase\
-nominlen -nomaxlen -norange -nochoices -nochoicelabels\
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\
}
error "punk::args::definition - unrecognised key '$k' in *leaders line. Known keys: $known"
}
}
}
}
values {
set argspace "values"
foreach {k v} $starspecs {
switch -- $k {
-min -
@ -610,11 +731,16 @@ tcl::namespace::eval punk::args {
}
default {
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *leaders *opts or *values - use **name if paramname needs to be *name"
}
}
continue
} elseif {$firstchar eq "-"} {
if {$argspace eq "leaders"} {
set argspace "options"
} elseif {$argspace eq "values"} {
error "punk::args::definition - invalid placement of line '$ln' - must come before *values"
}
set argspecs $linespecs
tcl::dict::set argspecs -ARGTYPE option
lappend opt_names $argname
@ -625,8 +751,16 @@ tcl::namespace::eval punk::args {
set argname [tcl::string::range $argname 1 end]
}
set argspecs $linespecs
if {$argspace eq "leaders"} {
tcl::dict::set argspecs -ARGTYPE leader
lappend leader_names $argname
if {$leader_max == 0} {
set leader_max [llength $leader_names]
}
} else {
tcl::dict::set argspecs -ARGTYPE value
lappend val_names $argname
}
set is_opt 0
}
#assert - we only get here if it is a value or flag specification line.
@ -634,7 +768,11 @@ tcl::namespace::eval punk::args {
if {$is_opt} {
set spec_merged $optspec_defaults
} else {
if {$argspace eq "values"} {
set spec_merged $valspec_defaults
} else {
set spec_merged $leaderspec_defaults
}
}
foreach {spec specval} $argspecs {
#literal-key switch - bytecompiled to jumpTable
@ -726,23 +864,37 @@ tcl::namespace::eval punk::args {
if {![tcl::dict::get $argspecs -optional] && ![tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
lappend opt_required $argname
} else {
if {$argspace eq "leaders"} {
lappend leader_required $argname
} else {
lappend val_required $argname
}
}
}
if {[tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
tcl::dict::set opt_defaults $argname [tcl::dict::get $argspecs -default]
} else {
if {$argspace eq "leaders"} {
tcl::dict::set leader_defaults $argname [tcl::dict::get $argspecs -default]
} else {
tcl::dict::set val_defaults $argname [tcl::dict::get $argspecs -default]
}
}
}
}
# REVIEW
foreach leadername [lrange $leader_names 0 end] {
if {[tcl::dict::get $arg_info $leadername -multiple]} {
error "bad key -multiple on argument spec for leader '$valname'. Only the last value argument specification can be marked -multiple"
}
}
#confirm any valnames before last don't have -multiple key
foreach valname [lrange $val_names 0 end-1] {
if {[tcl::dict::get $arg_info $valname -multiple]} {
error "bad key -multiple on argument spec for '$valname'. Only the last value argument specification can be marked -multiple"
error "bad key -multiple on argument spec for value '$valname'. Only the last value argument specification can be marked -multiple"
}
}
if {$spec_id eq "" || [tcl::string::tolower $spec_id] eq "auto"} {
@ -750,7 +902,11 @@ tcl::namespace::eval punk::args {
set spec_id "autoid_[incr id_counter]"
}
#todo - document that ambiguities in API are likely if both *leaders and *values used
#todo - do some checks for obvious bad definitions involving a mix of *leaders and *values (e.g with optional options)
set leader_checks_defaults [tcl::dict::remove $leaderspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set opt_checks_defaults [tcl::dict::remove $optspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set val_checks_defaults [tcl::dict::remove $valspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
@ -758,6 +914,13 @@ tcl::namespace::eval punk::args {
id $spec_id\
arg_info $arg_info\
arg_checks $arg_checks\
leader_defaults $leader_defaults\
leader_required $leader_required\
leader_names $leader_names\
leader_min $leader_min\
leader_max $leader_max\
leaderspec_defaults $leaderspec_defaults\
leader_checks_defaults $leader_checks_defaults\
opt_defaults $opt_defaults\
opt_required $opt_required\
opt_names $opt_names\
@ -782,13 +945,33 @@ tcl::namespace::eval punk::args {
return $result
}
proc get_spec {id} {
proc get_spec {id {patternlist *}} {
variable argspec_ids
if {[tcl::dict::exists $argspec_ids $id]} {
if {$patternlist eq "*"} {
return [tcl::dict::get $argspec_ids $id]
} else {
set spec [tcl::dict::get $argspec_ids $id]
set result ""
set specdict [definition $spec]
set arg_info [dict get $specdict arg_info]
foreach pat $patternlist {
set matches [dict keys $arg_info $pat]
foreach m $matches {
set def [dict get $arg_info $m]
set def [dict remove $def -ARGTYPE]
append result \n "$m $def"
}
}
return $result
}
}
return
}
#proc get_spec_leaders ??
#proc get_spec_opts ??
#proc get_spec_values ??
proc get_spec_ids {{match *}} {
variable argspec_ids
return [tcl::dict::keys $argspec_ids $match]
@ -876,17 +1059,17 @@ tcl::namespace::eval punk::args {
$t add_column -headers $blank_header_col
$t add_column -headers $blank_header_col
if {"$procname$prochelp" eq ""} {
$t configure_header 0 -values {Arg Type Default Multiple Help}
$t configure_header 0 -values {Arg Type Default Multi Help}
} elseif {$procname eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} elseif {$prochelp eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} else {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 2 -values {Arg Type Default Multiple Help}
$t configure_header 2 -values {Arg Type Default Multi Help}
}
@ -894,7 +1077,8 @@ tcl::namespace::eval punk::args {
#set A_DEFAULT [a+ brightwhite Brightgreen]
set A_DEFAULT ""
set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a]
set greencheck [a+ brightgreen]\u2713[a] ;#green tick
set soloflag [a+ brightcyan]\u2690[a] ;#flag - may be replacement char in old dos prompt (?)
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
@ -930,20 +1114,21 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names
}
}
set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names [dict get $spec_dict leader_names]
set trailing_val_names [dict get $spec_dict val_names]
#dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
# if {![string match -* $argname]} {
# lappend leading_val_names [lpop trailing_val_names 0]
# } else {
# break
# }
#}
#if {![llength $leading_val_names] && ![llength $opt_names]} {
# #all vals were actually trailing - no opts
# set trailing_val_names $leading_val_names
# set leading_val_names {}
#}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
@ -954,7 +1139,7 @@ tcl::namespace::eval punk::args {
foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} {
set default $A_DEFAULT[dict get $arginfo -default]$RST
set default "'$A_DEFAULT[dict get $arginfo -default]$RST'"
} else {
set default ""
}
@ -1022,7 +1207,14 @@ tcl::namespace::eval punk::args {
} else {
set multiple ""
}
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {[::punk::args::Dict_getdef $arginfo -optional 0] == 1 || [dict exists $arginfo -default]} {
set argshow "?${argshow}?"
}
set typeshow [dict get $arginfo -type]
if {$typeshow eq "none"} {
set typeshow "$typeshow $soloflag"
}
$t add_row [list $argshow $typeshow $default $multiple $help]
if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
}
@ -1087,7 +1279,7 @@ tcl::namespace::eval punk::args {
#[para]argumentname -key val -ky2 val2...
#[para]where the valid keys for each option specification are: -default -type -range -choices
#[para]comment lines begining with # are ignored and can be placed anywhere except within a multiline value where it would become part of that value
#[para]lines beginning with *proc *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]lines beginning with *proc *leaders *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]*opts or *values lines can appear multiple times with defaults affecting flags/values that follow.
#[arg_def list rawargs]
#[para] This is a list of the arguments to parse. Usually it will be the $args value from the containing proc,
@ -1162,17 +1354,55 @@ tcl::namespace::eval punk::args {
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
#dict for {a info} $arg_info {
# #todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
# if {![string match -* $a]} {
# #lappend pre_values [lpop rawargs 0]
# if {[catch {lpop rawargs 0} val]} {
# break
# } else {
# lappend pre_values $val
# }
# } else {
# break
# }
#}
set argnames [dict keys $arg_info]
set optnames [lsearch -all -inline $argnames -*]
set ridx 0
set rawargs_copy $rawargs
if {$leader_max != 0} {
foreach r $rawargs_copy {
if {$leader_max != -1 && $ridx > $leader_max-1} {
break
}
if {[string match -* $r]} {
if {$r eq "--"} {
break
}
set matchopt [::tcl::prefix::match -error {} $optnames $r]
if {$matchopt ne ""} {
#flaglike matches a known flag - don't treat as leader
break
}
if {![string match -* [lindex $argnames $ridx]]} {
#there is a named leading positional for this position
lappend pre_values [lpop rawargs 0]
incr ridx
continue
} else {
break
}
}
lappend pre_values [lpop rawargs 0]
incr ridx
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} {
set leaders [list]
set arglist {}
set post_values {}
#val_min, val_max
@ -1326,199 +1556,40 @@ tcl::namespace::eval punk::args {
break
}
}
set values [list {*}$pre_values {*}$post_values]
#set values [list {*}$pre_values {*}$post_values]
set leaders $pre_values
set values $post_values
} else {
set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set leaders $pre_values
set values $rawargs
#set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list]
}
#puts stderr "--> arglist: $arglist"
#puts stderr "--> values: $values"
}
if {$id eq "jtest"} {
if {[set eopts [lsearch -exact $rawargs "--"]] >= 0} {
lappend flagsreceived --
set values [lrange $rawargs $eopts+1 end]
set arglist [lrange $rawargs 0 $eopts-1]
set maxidx [expr {[llength $arglist]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $arglist $i]
if {![tcl::string::match -* $a]} {
#we can't treat as first positional arg - as it comes before the eopt indicator --
arg_error "punk::args::get_dict bad options for [Get_caller]. Expected flag (leading -) at position $i got:$rawargs" $argspecs
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $arglist $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $arglist $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
} else {
tcl::dict::set opts $a $newval
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
} else {
if {[lsearch $rawargs -*] >= 0} {
#no -- end of opts indicator
#to support option values with leading dash e.g -offset -1 , we can't just use the last flagindex to determine start of positional args.
#we break on first non-flag looking argument that isn't in an option's value position and use that index as the division.
#The caller should use -- if the first positional arg is likely or has the potential to start with a dash.
set maxidx [expr {[llength $rawargs]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $rawargs $i]
#we can automatically rule out arguments containing whitespace from the set of simple flags beginning with a dash
#This helps for example when first value is a dict or list in which the first item happens to begin with a dash
#explicit -- still safer in many cases, but this is a reasonable and fast enough test
if {![tcl::string::match -* $a] || [regexp {\s+} $a]} {
#assume beginning of positional args
incr i -1
set positionalidx 0 ;#index for unnamed positionals (both leaders and values)
set ldridx 0
set leadernames_received [list]
set leaders_dict $leader_defaults
set num_leaders [llength $leaders]
foreach leadername $leader_names ldr $leaders {
if {$ldridx+1 > $num_leaders} {
break
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $rawargs $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $rawargs $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
if {$leadername ne ""} {
tcl::dict::set leaders_dict $leadername $ldr
lappend leadernames_received $leadername
} else {
tcl::dict::set opts $a $newval
tcl::dict::set leaders_dict $positionalidx $ldr
tcl::dict::set arg_info $positionalidx $leaderspec_defaults
tcl::dict::set arg_checks $positionalidx $leader_checks_defaults
lappend leadernames_received $positionalidx
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
incr ldridx
incr positionalidx
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end]
#puts "$i--->arglist:$arglist"
#puts "$i--->values:$values"
} else {
set values $rawargs ;#no -flags detected
set arglist [list]
}
}
}
set validx 0
set in_multiple ""
@ -1547,13 +1618,29 @@ tcl::namespace::eval punk::args {
tcl::dict::lappend values_dict $in_multiple $val
#name already seen
} else {
tcl::dict::set values_dict $validx $val
tcl::dict::set arg_info $validx $valspec_defaults
tcl::dict::set arg_checks $validx $val_checks_defaults
lappend valnames_received $validx
tcl::dict::set values_dict $positionalidx $val
tcl::dict::set arg_info $positionalidx $valspec_defaults
tcl::dict::set arg_checks $positionalidx $val_checks_defaults
lappend valnames_received $positionalidx
}
}
incr validx
incr positionalidx
}
if {$leader_max == -1} {
#only check min
if {$num_leaders < $leader_min} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected at least $leader_min" $argspecs
}
} else {
if {$num_leaders < $leader_min || $num_leaders > $leader_max} {
if {$leader_min == $leader_max} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected exactly $leader_min" $argspecs
} else {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected between $leader_min and $leader_max inclusive" $argspecs
}
}
}
if {$val_max == -1} {
@ -1592,6 +1679,9 @@ tcl::namespace::eval punk::args {
# error "Required value missing for [Get_caller]. missing values $missing marked with -optional false - so must be present"
#}
#for now (2024-06) punk::lib::ldiff is a better compromise across normal/safe interps e.g 0.7/0.8us
if {[llength [set missing [punklib_ldiff $leader_required $leadernames_received]]]} {
arg_error "Required leader missing for [Get_caller]. missing values: '$missing' marked with -optional false - so must be present" $argspecs
}
if {[llength [set missing [punklib_ldiff $opt_required $flagsreceived]]]} {
arg_error "Required option missing for [Get_caller]. missing flags: '$missing' are marked with -optional false - so must be present in full-length form" $argspecs
}
@ -1605,7 +1695,7 @@ tcl::namespace::eval punk::args {
#todo - allow defaults outside of choices/ranges
#check types,ranges,choices
set opts_and_values [tcl::dict::merge $opts $values_dict]
set opts_and_values [tcl::dict::merge $leaders_dict $opts $values_dict]
#set combined_defaults [tcl::dict::merge $val_defaults $opt_defaults] ;#can be no key collisions - we don't allow a value key beginning with dash - opt names must begin with dash
#puts "---opts_and_values:$opts_and_values"
#puts "---arg_info:$arg_info"
@ -1657,11 +1747,17 @@ tcl::namespace::eval punk::args {
set choiceprefix [tcl::dict::get $thisarg -choiceprefix]
set choicerestricted [tcl::dict::get $thisarg -choicerestricted]
set nocase [tcl::dict::get $thisarg -nocase]
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
set dname leaders_dict
}
option {
set dname opts
} else {
}
value {
set dname values_dict
}
}
set idx 0 ;#
#opts/values_dict $argname membmer has been populated with the actual entered choices - which might be prefixes
#assert llength $vlist == llength [dict get $dname $argname]
@ -1819,8 +1915,8 @@ tcl::namespace::eval punk::args {
lappend pass_quick_list_e_check $e_check
}
}
set remaining_e [punk::lib::ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punk::lib::ldiff $vlist_check $pass_quick_list_e_check]
set remaining_e [punklib_ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punklib_ldiff $vlist_check $pass_quick_list_e_check]
}
if {$regexprefail ne ""} {
foreach e $remaining_e e_check $remaining_e_check {
@ -2036,23 +2132,37 @@ tcl::namespace::eval punk::args {
if {$is_strip_ansi} {
set stripped_list [lmap e $vlist_original {punk::ansi::ansistrip $e}] ;#no faster or slower, but more concise than foreach
if {[tcl::dict::get $thisarg -multiple]} {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict $argname $stripped_list
}
option {
tcl::dict::set opts $argname $stripped_list
} else {
}
value {
tcl::dict::set values_dict $argname $stripped_list
}
}
} else {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict [lindex $stripped_list 0]
}
option {
tcl::dict::set opts $argname [lindex $stripped_list 0]
} else {
}
value {
tcl::dict::set values_dict [lindex $stripped_list 0]
}
}
}
}
}
#maintain order of opts $opts values $values as caller may use lassign.
return [tcl::dict::create opts $opts values $values_dict]
set receivednames [list {*}$leadernames_received {*}$flagsreceived {*}$valnames_received]
set received_posns [concat {*}[lmap a $receivednames b [zero_to_n [expr {[llength $receivednames]-1}]] {list $a $b}]] ;#flat zip named with overall posn including opts
return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns]
}
#proc sample1 {p1 args} {
@ -2063,6 +2173,16 @@ tcl::namespace::eval punk::args {
#}
punk::args::definition {
*id punk::args::TEST
*opts -optional 0
-o1 -default 111 -help "opt 1 mandatory"
*opts -optional 1
-o2 -default 222 -help "opt 2 optional"
*values -min 0 -max 1
v -help\
"v1 optional"
}
#*** !doctools

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm

@ -1556,7 +1556,7 @@ namespace eval punk::fileline::lib {
set argd [punk::args::get_dict {
-offset -default 0
} $args]
lassign [dict values $argd] opts remainingargs
lassign [dict values $argd] leaders opts remainingargs
}

81
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm

@ -211,6 +211,9 @@ tcl::namespace::eval punk::lib::compat {
#*** !doctools
#[list_begin definitions]
if {"::lremove" ne [info commands ::lremove]} {
#puts stderr "Warning - no built-in lremove"
interp alias {} lremove {} ::punk::lib::compat::lremove
@ -393,6 +396,80 @@ namespace eval punk::lib {
set has_twapi [expr {![catch {package require twapi}]}]
}
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::lib::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::lib::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
# == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
# Maintenance - This is the primary source for tm_version... functions
# - certain packages script require these but without package dependency
@ -2894,7 +2971,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
-joinchar -default \n
*values -min 1 -max 1
} $args]] opts values
} $args]] leaders opts values
puts "opts:$opts"
puts "values:$values"
return [join [tcl::dict::get $values 0] [tcl::dict::get $opts -joinchar]]
@ -2936,7 +3013,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
*opts -any 1
-block -default {}
} $args]] opts valuedict
} $args]] leaderdict opts valuedict
tailcall linelist {*}$opts {*}[tcl::dict::values $valuedict]
}

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm

@ -153,7 +153,7 @@ namespace eval punk::mix::commandset::module {
module -type string
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set module [dict get $values module]
#set opts [dict merge $defaults $args]

7
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm

@ -643,7 +643,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values_dict
lassign [dict values $argd] leaders opts values_dict
set opt_stripbase [dict get $opts -stripbase]
set opt_formatsizes [dict get $opts -formatsizes]
@ -726,6 +726,7 @@ tcl::namespace::eval punk::nav::fs {
# -searchbase is always passed through - and is only used to construct a location path if a relative searchspec was supplied
proc dirfiles_dict {args} {
set argspecs {
*id punk::nav::fs::dirfiles_dict
*opts -any 0
-searchbase -default ""
-tailglob -default "\uFFFF"
@ -735,7 +736,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1 -type string
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
#puts stderr "searchspecs: $searchspecs [llength $searchspecs]"
@ -1000,7 +1001,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 1 -max -1 -type dict
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set list_of_dicts [dict values $vals]

40
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm

@ -20,8 +20,8 @@
package require punk::lib
package require punk::args
tcl::namespace::eval ::punk_dynamic::ns {
tcl::namespace::eval ::punk::ns::evaluator {
#eval-_NS_xxx_NS_etc procs
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -29,6 +29,12 @@ tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nsimport_noclobber corp
catch {
package require debug
debug define punk.ns.compile
#debug on punk.ns.compile
#debug level punk.ns.compile 3
}
#leading colon makes it hard (impossible?) to call directly if not within the namespace
proc ns/ {v {ns_or_glob ""} args} {
@ -200,16 +206,17 @@ tcl::namespace::eval punk::ns {
return $body
}
proc nseval {fqns script} {
#create one proc for each fully qualified namespace to evaluate script
if {![string match ::* $fqns]} {
error "nseval only accepts a fully qualified namespace"
}
set loc [string map {:: _sep_} $fqns]
set loc [string map {:: _NS_} $fqns]
#set cmd ::punk::pipecmds::nseval_$loc
set cmd ::punk_dynamic::ns::eval-$loc
set cmd ::punk::ns::evaluator::eval-$loc
if {$cmd ni [info commands $cmd]} {
append body \n [nseval_script $fqns]
proc $cmd {script} $body
debug.punk.pipe.compile {proc $cmd} 6
debug.punk.ns.compile {proc $cmd} 2
}
tailcall $cmd $script
}
@ -800,14 +807,23 @@ tcl::namespace::eval punk::ns {
set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
#set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set lenlist1 [lmap v [list {*}$children1 ""] {string length $v}]
set chwidest1 [tcl::mathfunc::max {*}$lenlist1]
#set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$children2 ""] {string length $v}]]
#wrap the cmd in [list] (just for the width calc) to get a proper length for what will actually be displayed
set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
#set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest1 [tcl::mathfunc::max {*}[lmap v [list {*}$elements1 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$elements2 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [tcl::mathfunc::max {*}[lmap v [list {*}$elements3 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [tcl::mathfunc::max {*}[lmap v [list {*}$elements4 ""] {string length [list [lindex $v 1]]}]]
set displaylist [list]
set col1 [string repeat " " [expr {$chwidest1 + 8}]]
@ -1875,7 +1891,7 @@ tcl::namespace::eval punk::ns {
*values -min 1 -max 1
sourcepattern -type string -optional 0
}
lassign [dict values [punk::args::get_dict $argspecs $args]] opts values
lassign [dict values [punk::args::get_dict $argspecs $args]] leaders opts values
set sourcepattern [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm

@ -662,7 +662,7 @@ namespace eval punk::path {
*values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1
} $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set tailglobs [dict values $values]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]

36
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm

@ -13,12 +13,24 @@
tcl::namespace::eval shellfilter::log {
variable allow_adhoc_tags 1
variable open_logs [tcl::dict::create]
variable is_enabled 0
proc disable {} {
variable is_enabled
set is_enabled 0
proc ::shellfilter::log::open {tag settingsdict} {}
proc ::shellfilter::log::write {tag msg} {}
proc ::shellfilter::log::write_sync {tag msg} {}
proc ::shellfilter::log::close {tag} {}
}
proc enable {} {
variable is_enabled
set is_enabled 1
#'tag' is an identifier for the log source.
# each tag will use it's own thread to write to the configured log target
proc open {tag {settingsdict {}}} {
proc ::shellfilter::log::open {tag {settingsdict {}}} {
upvar ::shellfilter::sources sourcelist
package require shellthread
if {![dict exists $settingsdict -tag]} {
tcl::dict::set settingsdict -tag $tag
} else {
@ -36,7 +48,7 @@ tcl::namespace::eval shellfilter::log {
#puts stderr "shellfilter::log::open this_threadid: [thread::id] tag: $tag worker_tid: $worker_tid"
return $worker_tid
}
proc write {tag msg} {
proc ::shellfilter::log::write {tag msg} {
upvar ::shellfilter::sources sourcelist
variable allow_adhoc_tags
if {!$allow_adhoc_tags} {
@ -47,14 +59,16 @@ tcl::namespace::eval shellfilter::log {
shellthread::manager::write_log $tag $msg
}
#write_sync - synchronous processing with logging thread, slower but potentially useful for debugging/testing or forcing delay til log written
proc write_sync {tag msg} {
proc ::shellfilter::log::write_sync {tag msg} {
shellthread::manager::write_log $tag $msg -async 0
}
proc close {tag} {
proc ::shellfilter::log::close {tag} {
#shellthread::manager::close_worker $tag
shellthread::manager::unsubscribe [list $tag]; #workertid will be added back to free list if no tags remain subscribed
}
}
#review
#configure whether we can call shellfilter::log::write without having called open first
proc require_open {{is_open_required {}}} {
@ -73,6 +87,12 @@ tcl::namespace::eval shellfilter::log {
}
}
}
if {[catch {package require shellthread}]} {
shellfilter::log::disable
} else {
shellfilter::log::enable
}
}
namespace eval shellfilter::pipe {
#write channel for program. workerthread reads other end of fifo2 and writes data somewhere
@ -1594,7 +1614,13 @@ namespace eval shellfilter::stack {
set tag "SHELLFILTER::STACK"
#JMN - load from config
#::shellfilter::log::open $tag {-syslog 127.0.0.1:514}
if {[catch {
::shellfilter::log::open $tag {-syslog ""}
} err]} {
#e.g safebase interp can't load required modules such as shellthread (or Thread)
puts stderr "shellfilter::show_pipeline cannot open log"
return
}
::shellfilter::log::write $tag "transform stack for $pipename $args"
foreach tf $stack {
::shellfilter::log::write $tag " $tf"

15
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.2.tm

@ -62,7 +62,14 @@ catch {package require patternpunk}
package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
if {[catch {
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
} errM]} {
#catch this too in case stderr not available
catch {
puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
}
}
package require textutil
@ -5182,7 +5189,7 @@ tcl::namespace::eval textblock {
lassign [punk::args::get_dict {
-ansiresets -default 1 -type integer
blocks -type string -multiple 1
} $args] _o opts _v values
} $args] _l leaders _o opts _v values
set blocks [tcl::dict::get $values blocks]
set idx 0
@ -5844,7 +5851,7 @@ tcl::namespace::eval textblock {
*values -min 1
frametype -choices "<ftlist>" -choiceprefix 0 -choicerestricted 0 -type dict\
-help "name from the predefined frametypes or an adhoc dictionary."
memberglob -type globstring -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
memberglob -type globstring -optional 1 -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
corner noncorner top bottom vertical horizontal left right
hl hlt hlb vsl vll vlr tlc trc blc brc hltj hlbj vllj vlrj
}\
@ -8110,7 +8117,7 @@ tcl::namespace::eval textblock {
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
*values -min 0 -max 1
size -default 1 -type integer
}
proc gcross {args} {

74
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.1.tm

@ -6976,7 +6976,7 @@ namespace eval punk {
-punctchars -default { [list \{ \} \" \\ - _ + = . > , < ' : \; ` ~ ! @ # \$ % ^ & * \[ \] ( ) | / ?] }
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
# -- --- --- --- --- ---
@ -7560,79 +7560,13 @@ namespace eval punk {
#this hides windows cmd's mode command - probably no big deal - anyone who needs it will know how to exec it.
interp alias {} mode {} punk::mode
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
tailcall punk::lib::aliases $glob
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
tailcall punk::lib::alias $aliasorglob {*}$args
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
#pipeline-toys - put in lib/scriptlib?
##geometric mean
@ -7669,8 +7603,6 @@ namespace eval punk {
interp alias {} help {} punk::help
interp alias {} aliases {} punk::aliases
interp alias {} alias {} punk::alias
interp alias {} treemore {} punk::xmore tree
interp alias {} tmhere {} .= pwd |path> {::tcl::tm::add {*}$data; set path} |> inspect -label added_to_module_path <0/#|

36
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -105,6 +105,8 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
list_as_lines ::punk::lib::list_as_lines\
lines_as_list ::punk::lib::lines_as_list\
@ -155,34 +157,48 @@ tcl::namespace::eval punk::aliascore {
set opts [dict merge $defaults $args]
set opt_force [dict get $opts -force]
#we never override existing aliases to ::repl::interp* even if -force = 1
#(these are our safebase aliases)
set ignore_pattern "::repl::interp*"
set ignore_aliases [list]
variable aliases
if {!$opt_force} {
set existing [list]
set conflicts [list]
foreach {a cmd} $aliases {
if {[tcl::info::commands ::$a] ne ""} {
lappend existing $a
if {[llength $cmd] > 1} {
#use alias mechanism
set existing_target [interp alias "" $a]
set existing_alias [interp alias "" $a]
if {$existing_alias ne ""} {
set existing_target $existing_alias
if {[string match $ignore_pattern $existing_target]} {
#don't consider it a conflict - will use ignore_aliases to exclude it below
lappend ignore_aliases $a
continue
}
} else {
#using namespace import
#check origin
set existing_target [tcl::namespace::origin $cmd]
if {[catch {tcl::namespace::origin $a} existing_command]} {
set existing_command ""
}
set existing_target $existing_command
}
if {$existing_target ne $cmd} {
#command exists in global ns but doesn't match our defined aliases/imports
lappend conflicts $a
}
}
}
if {[llength $conflicts]} {
if {!$opt_force && [llength $conflicts]} {
error "punk::aliascore::init declined to create any aliases or imports because -force == 0 and conflicts found:$conflicts"
}
}
set tempns ::temp_[info cmdcount] ;#temp ns for renames
dict for {a cmd} $aliases {
#puts "aliascore $a -> $cmd"
if {$a in $ignore_aliases} {
continue
}
if {[llength $cmd] > 1} {
interp alias {} $a {} {*}$cmd
} else {
@ -205,7 +221,7 @@ tcl::namespace::eval punk::aliascore {
}
}
#tcl::namespace::delete $tempns
return [dict keys $aliases]
return [dict create aliases [dict keys $aliases] unchanged $ignore_aliases changed $conflicts]
}

576
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.1.0.tm

@ -58,7 +58,7 @@
# #setting -type none indicates a flag that doesn't take a value (solo flag)
# -nocomplain -type none
# *values -min 1 -max -1
# } $args]] opts values
# } $args]] leaders opts values
#
# puts "translation is [dict get $opts -translation]"
# foreach f [dict values $values] {
@ -68,7 +68,7 @@
#}]
#[para]The lines beginning with * are optional in most cases and can be used to set defaults and some extra controls
#[para] - the above example would work just fine with only the -<optionname> lines, but would allow zero filenames to be supplied as no -min value is set for *values
#[para]valid * lines being with *proc *opts *values
#[para]valid * lines being with *proc *leaders *opts *values
#[para]lines beginning with a dash define options - a name can optionally be given to each trailing positional argument.
#[para]If no names are defined for positional arguments, they will end up in the values key of the dict with numerical keys starting at zero.
#[para]e.g the result from the punk::args call above may be something like:
@ -299,6 +299,22 @@ tcl::namespace::eval punk::args {
}
return $result
}
proc flatzip {l1 l2} {
concat {*}[lmap a $l1 b $l2 {list $a $b}]
}
if {[info commands lseq] ne ""} {
#tcl 8.7+ lseq significantly faster, especially for larger ranges
#The internal rep can be an 'arithseries' with no string representation
proc zero_to_n {n} {
lseq 0 $n
}
} else {
proc zero_to_n {n} {
lsearch -all [lrepeat $n 0] *
}
}
#todo? -synonym/alias ? (applies to opts only not values)
#e.g -background -aliases {-bg} -default White
@ -309,8 +325,8 @@ tcl::namespace::eval punk::args {
variable argspec_cache
#variable argspecs ;#REVIEW!!
variable argspec_ids
variable initial_optspec_defaults
variable initial_valspec_defaults
#variable initial_optspec_defaults
#variable initial_valspec_defaults
#ideally we would use a fast hash algorithm to produce a short key with low collision probability.
#something like md5 would be ok (this is non cryptographic) - but md5 uses open and isn't usable by default in a safe interp.
#review - check if there is a built-into-tcl way to do this quickly
@ -335,6 +351,19 @@ tcl::namespace::eval punk::args {
-regexprepass {}\
-validationtransform {}\
]
set leaderspec_defaults [tcl::dict::create\
-type string\
-optional 0\
-allow_ansi 1\
-validate_ansistripped 0\
-strip_ansi 0\
-nocase 0\
-choiceprefix 1\
-choicerestricted 1\
-multiple 0\
-regexprepass {}\
-validationtransform {}\
]
set valspec_defaults [tcl::dict::create\
-type string\
-optional 0\
@ -357,15 +386,20 @@ tcl::namespace::eval punk::args {
#default -allow_ansi to 1 and -validate_ansistripped to 0 and -strip_ansi 0 - it takes time to strip ansi
#todo - detect if anything in the spec uses -allow_ansi 0, -validate_ansistripped 1 or -strip_ansi 1 and set a flag indicating if punk::ansi::ta::detect should be run on the argslist
set leader_required [list]
set opt_required [list]
set val_required [list]
set arg_info [tcl::dict::create]
set arg_checks [tcl::dict::create]
set opt_defaults [tcl::dict::create]
set opt_names [list] ;#defined opts
set leader_defaults [tcl::dict::create]
set val_defaults [tcl::dict::create]
set opt_solos [list]
#first process dashed and non-dashed argspecs without regard to whether non-dashed are at the beginning or end
set leader_names [list]
set val_names [list]
set records [list]
@ -439,10 +473,14 @@ tcl::namespace::eval punk::args {
}
set proc_info {}
set id_info {} ;#e.g -children <list> ??
set leader_min 0
set leader_max 0 ;#default 0 but if leader_min is set switches to -1 for no limit
#(common case of no leaders specified)
set opt_any 0
set val_min 0
set val_max -1 ;#-1 for no limit
set spec_id ""
set argspace "leaders" ;#leaders -> options -> values
foreach ln $records {
set trimln [tcl::string::trim $ln]
switch -- [tcl::string::index $trimln 0] {
@ -477,6 +515,10 @@ tcl::namespace::eval punk::args {
set proc_info $starspecs
}
opts {
if {$argspace eq "values"} {
error "punk::args::definition - *opts declaration must come before *values - received '$linespecs'"
}
set argspace "options"
foreach {k v} $starspecs {
switch -- $k {
-any -
@ -537,7 +579,86 @@ tcl::namespace::eval punk::args {
}
}
}
leaders {
if {$argspace in [list options values]} {
error "punk::args::definition - *leaders declaration must come before all options and values"
}
foreach {k v} $starspecs {
switch -- $k {
-min -
-minvalues {
if {$v < 0} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
}
set leader_min $v
if {$leader_max == 0} {
set leader_max -1
}
}
-max -
-maxvalues {
if {$v < -1} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
}
set leader_max $v
}
-minlen - -maxlen - -range - -choices - -choicelabels - -choiceprefix - -choicerestricted - -nocase {
#review - only apply to certain types?
tcl::dict::set leaderspec_defaults $k $v
}
-nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels {
if {$v} {
tcl::dict::unset leaderspec_defaults $k
}
}
-type {
switch -- $v {
int - integer {
set v int
}
char - character {
set v char
}
bool - boolean {
set v bool
}
dict - dictionary {
set v dict
}
list {
}
default {
#todo - disallow unknown types unless prefixed with custom-
}
}
tcl::dict::set leaderspec_defaults $k $v
}
-optional -
-allow_ansi -
-validate_ansistripped -
-strip_ansi -
-regexprepass -
-regexprefail -
-validationtransform -
-multiple {
tcl::dict::set leaderspec_defaults $k $v
}
default {
set known { -min -minvalues -max -maxvalues\
-minlen -maxlen -range -choices -choicelabels -choiceprefix -choicerestricted -nocase\
-nominlen -nomaxlen -norange -nochoices -nochoicelabels\
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\
}
error "punk::args::definition - unrecognised key '$k' in *leaders line. Known keys: $known"
}
}
}
}
values {
set argspace "values"
foreach {k v} $starspecs {
switch -- $k {
-min -
@ -610,11 +731,16 @@ tcl::namespace::eval punk::args {
}
default {
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *leaders *opts or *values - use **name if paramname needs to be *name"
}
}
continue
} elseif {$firstchar eq "-"} {
if {$argspace eq "leaders"} {
set argspace "options"
} elseif {$argspace eq "values"} {
error "punk::args::definition - invalid placement of line '$ln' - must come before *values"
}
set argspecs $linespecs
tcl::dict::set argspecs -ARGTYPE option
lappend opt_names $argname
@ -625,8 +751,16 @@ tcl::namespace::eval punk::args {
set argname [tcl::string::range $argname 1 end]
}
set argspecs $linespecs
if {$argspace eq "leaders"} {
tcl::dict::set argspecs -ARGTYPE leader
lappend leader_names $argname
if {$leader_max == 0} {
set leader_max [llength $leader_names]
}
} else {
tcl::dict::set argspecs -ARGTYPE value
lappend val_names $argname
}
set is_opt 0
}
#assert - we only get here if it is a value or flag specification line.
@ -634,7 +768,11 @@ tcl::namespace::eval punk::args {
if {$is_opt} {
set spec_merged $optspec_defaults
} else {
if {$argspace eq "values"} {
set spec_merged $valspec_defaults
} else {
set spec_merged $leaderspec_defaults
}
}
foreach {spec specval} $argspecs {
#literal-key switch - bytecompiled to jumpTable
@ -726,23 +864,37 @@ tcl::namespace::eval punk::args {
if {![tcl::dict::get $argspecs -optional] && ![tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
lappend opt_required $argname
} else {
if {$argspace eq "leaders"} {
lappend leader_required $argname
} else {
lappend val_required $argname
}
}
}
if {[tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
tcl::dict::set opt_defaults $argname [tcl::dict::get $argspecs -default]
} else {
if {$argspace eq "leaders"} {
tcl::dict::set leader_defaults $argname [tcl::dict::get $argspecs -default]
} else {
tcl::dict::set val_defaults $argname [tcl::dict::get $argspecs -default]
}
}
}
}
# REVIEW
foreach leadername [lrange $leader_names 0 end] {
if {[tcl::dict::get $arg_info $leadername -multiple]} {
error "bad key -multiple on argument spec for leader '$valname'. Only the last value argument specification can be marked -multiple"
}
}
#confirm any valnames before last don't have -multiple key
foreach valname [lrange $val_names 0 end-1] {
if {[tcl::dict::get $arg_info $valname -multiple]} {
error "bad key -multiple on argument spec for '$valname'. Only the last value argument specification can be marked -multiple"
error "bad key -multiple on argument spec for value '$valname'. Only the last value argument specification can be marked -multiple"
}
}
if {$spec_id eq "" || [tcl::string::tolower $spec_id] eq "auto"} {
@ -750,7 +902,11 @@ tcl::namespace::eval punk::args {
set spec_id "autoid_[incr id_counter]"
}
#todo - document that ambiguities in API are likely if both *leaders and *values used
#todo - do some checks for obvious bad definitions involving a mix of *leaders and *values (e.g with optional options)
set leader_checks_defaults [tcl::dict::remove $leaderspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set opt_checks_defaults [tcl::dict::remove $optspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set val_checks_defaults [tcl::dict::remove $valspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
@ -758,6 +914,13 @@ tcl::namespace::eval punk::args {
id $spec_id\
arg_info $arg_info\
arg_checks $arg_checks\
leader_defaults $leader_defaults\
leader_required $leader_required\
leader_names $leader_names\
leader_min $leader_min\
leader_max $leader_max\
leaderspec_defaults $leaderspec_defaults\
leader_checks_defaults $leader_checks_defaults\
opt_defaults $opt_defaults\
opt_required $opt_required\
opt_names $opt_names\
@ -782,13 +945,33 @@ tcl::namespace::eval punk::args {
return $result
}
proc get_spec {id} {
proc get_spec {id {patternlist *}} {
variable argspec_ids
if {[tcl::dict::exists $argspec_ids $id]} {
if {$patternlist eq "*"} {
return [tcl::dict::get $argspec_ids $id]
} else {
set spec [tcl::dict::get $argspec_ids $id]
set result ""
set specdict [definition $spec]
set arg_info [dict get $specdict arg_info]
foreach pat $patternlist {
set matches [dict keys $arg_info $pat]
foreach m $matches {
set def [dict get $arg_info $m]
set def [dict remove $def -ARGTYPE]
append result \n "$m $def"
}
}
return $result
}
}
return
}
#proc get_spec_leaders ??
#proc get_spec_opts ??
#proc get_spec_values ??
proc get_spec_ids {{match *}} {
variable argspec_ids
return [tcl::dict::keys $argspec_ids $match]
@ -876,17 +1059,17 @@ tcl::namespace::eval punk::args {
$t add_column -headers $blank_header_col
$t add_column -headers $blank_header_col
if {"$procname$prochelp" eq ""} {
$t configure_header 0 -values {Arg Type Default Multiple Help}
$t configure_header 0 -values {Arg Type Default Multi Help}
} elseif {$procname eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} elseif {$prochelp eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} else {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 2 -values {Arg Type Default Multiple Help}
$t configure_header 2 -values {Arg Type Default Multi Help}
}
@ -894,7 +1077,8 @@ tcl::namespace::eval punk::args {
#set A_DEFAULT [a+ brightwhite Brightgreen]
set A_DEFAULT ""
set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a]
set greencheck [a+ brightgreen]\u2713[a] ;#green tick
set soloflag [a+ brightcyan]\u2690[a] ;#flag - may be replacement char in old dos prompt (?)
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
@ -930,20 +1114,21 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names
}
}
set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names [dict get $spec_dict leader_names]
set trailing_val_names [dict get $spec_dict val_names]
#dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
# if {![string match -* $argname]} {
# lappend leading_val_names [lpop trailing_val_names 0]
# } else {
# break
# }
#}
#if {![llength $leading_val_names] && ![llength $opt_names]} {
# #all vals were actually trailing - no opts
# set trailing_val_names $leading_val_names
# set leading_val_names {}
#}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
@ -954,7 +1139,7 @@ tcl::namespace::eval punk::args {
foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} {
set default $A_DEFAULT[dict get $arginfo -default]$RST
set default "'$A_DEFAULT[dict get $arginfo -default]$RST'"
} else {
set default ""
}
@ -1022,7 +1207,14 @@ tcl::namespace::eval punk::args {
} else {
set multiple ""
}
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {[::punk::args::Dict_getdef $arginfo -optional 0] == 1 || [dict exists $arginfo -default]} {
set argshow "?${argshow}?"
}
set typeshow [dict get $arginfo -type]
if {$typeshow eq "none"} {
set typeshow "$typeshow $soloflag"
}
$t add_row [list $argshow $typeshow $default $multiple $help]
if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
}
@ -1087,7 +1279,7 @@ tcl::namespace::eval punk::args {
#[para]argumentname -key val -ky2 val2...
#[para]where the valid keys for each option specification are: -default -type -range -choices
#[para]comment lines begining with # are ignored and can be placed anywhere except within a multiline value where it would become part of that value
#[para]lines beginning with *proc *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]lines beginning with *proc *leaders *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]*opts or *values lines can appear multiple times with defaults affecting flags/values that follow.
#[arg_def list rawargs]
#[para] This is a list of the arguments to parse. Usually it will be the $args value from the containing proc,
@ -1162,17 +1354,55 @@ tcl::namespace::eval punk::args {
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
#dict for {a info} $arg_info {
# #todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
# if {![string match -* $a]} {
# #lappend pre_values [lpop rawargs 0]
# if {[catch {lpop rawargs 0} val]} {
# break
# } else {
# lappend pre_values $val
# }
# } else {
# break
# }
#}
set argnames [dict keys $arg_info]
set optnames [lsearch -all -inline $argnames -*]
set ridx 0
set rawargs_copy $rawargs
if {$leader_max != 0} {
foreach r $rawargs_copy {
if {$leader_max != -1 && $ridx > $leader_max-1} {
break
}
if {[string match -* $r]} {
if {$r eq "--"} {
break
}
set matchopt [::tcl::prefix::match -error {} $optnames $r]
if {$matchopt ne ""} {
#flaglike matches a known flag - don't treat as leader
break
}
if {![string match -* [lindex $argnames $ridx]]} {
#there is a named leading positional for this position
lappend pre_values [lpop rawargs 0]
incr ridx
continue
} else {
break
}
}
lappend pre_values [lpop rawargs 0]
incr ridx
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} {
set leaders [list]
set arglist {}
set post_values {}
#val_min, val_max
@ -1326,199 +1556,40 @@ tcl::namespace::eval punk::args {
break
}
}
set values [list {*}$pre_values {*}$post_values]
#set values [list {*}$pre_values {*}$post_values]
set leaders $pre_values
set values $post_values
} else {
set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set leaders $pre_values
set values $rawargs
#set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list]
}
#puts stderr "--> arglist: $arglist"
#puts stderr "--> values: $values"
}
if {$id eq "jtest"} {
if {[set eopts [lsearch -exact $rawargs "--"]] >= 0} {
lappend flagsreceived --
set values [lrange $rawargs $eopts+1 end]
set arglist [lrange $rawargs 0 $eopts-1]
set maxidx [expr {[llength $arglist]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $arglist $i]
if {![tcl::string::match -* $a]} {
#we can't treat as first positional arg - as it comes before the eopt indicator --
arg_error "punk::args::get_dict bad options for [Get_caller]. Expected flag (leading -) at position $i got:$rawargs" $argspecs
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $arglist $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $arglist $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
} else {
tcl::dict::set opts $a $newval
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
} else {
if {[lsearch $rawargs -*] >= 0} {
#no -- end of opts indicator
#to support option values with leading dash e.g -offset -1 , we can't just use the last flagindex to determine start of positional args.
#we break on first non-flag looking argument that isn't in an option's value position and use that index as the division.
#The caller should use -- if the first positional arg is likely or has the potential to start with a dash.
set maxidx [expr {[llength $rawargs]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $rawargs $i]
#we can automatically rule out arguments containing whitespace from the set of simple flags beginning with a dash
#This helps for example when first value is a dict or list in which the first item happens to begin with a dash
#explicit -- still safer in many cases, but this is a reasonable and fast enough test
if {![tcl::string::match -* $a] || [regexp {\s+} $a]} {
#assume beginning of positional args
incr i -1
set positionalidx 0 ;#index for unnamed positionals (both leaders and values)
set ldridx 0
set leadernames_received [list]
set leaders_dict $leader_defaults
set num_leaders [llength $leaders]
foreach leadername $leader_names ldr $leaders {
if {$ldridx+1 > $num_leaders} {
break
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $rawargs $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $rawargs $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
if {$leadername ne ""} {
tcl::dict::set leaders_dict $leadername $ldr
lappend leadernames_received $leadername
} else {
tcl::dict::set opts $a $newval
tcl::dict::set leaders_dict $positionalidx $ldr
tcl::dict::set arg_info $positionalidx $leaderspec_defaults
tcl::dict::set arg_checks $positionalidx $leader_checks_defaults
lappend leadernames_received $positionalidx
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
incr ldridx
incr positionalidx
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end]
#puts "$i--->arglist:$arglist"
#puts "$i--->values:$values"
} else {
set values $rawargs ;#no -flags detected
set arglist [list]
}
}
}
set validx 0
set in_multiple ""
@ -1547,13 +1618,29 @@ tcl::namespace::eval punk::args {
tcl::dict::lappend values_dict $in_multiple $val
#name already seen
} else {
tcl::dict::set values_dict $validx $val
tcl::dict::set arg_info $validx $valspec_defaults
tcl::dict::set arg_checks $validx $val_checks_defaults
lappend valnames_received $validx
tcl::dict::set values_dict $positionalidx $val
tcl::dict::set arg_info $positionalidx $valspec_defaults
tcl::dict::set arg_checks $positionalidx $val_checks_defaults
lappend valnames_received $positionalidx
}
}
incr validx
incr positionalidx
}
if {$leader_max == -1} {
#only check min
if {$num_leaders < $leader_min} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected at least $leader_min" $argspecs
}
} else {
if {$num_leaders < $leader_min || $num_leaders > $leader_max} {
if {$leader_min == $leader_max} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected exactly $leader_min" $argspecs
} else {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected between $leader_min and $leader_max inclusive" $argspecs
}
}
}
if {$val_max == -1} {
@ -1592,6 +1679,9 @@ tcl::namespace::eval punk::args {
# error "Required value missing for [Get_caller]. missing values $missing marked with -optional false - so must be present"
#}
#for now (2024-06) punk::lib::ldiff is a better compromise across normal/safe interps e.g 0.7/0.8us
if {[llength [set missing [punklib_ldiff $leader_required $leadernames_received]]]} {
arg_error "Required leader missing for [Get_caller]. missing values: '$missing' marked with -optional false - so must be present" $argspecs
}
if {[llength [set missing [punklib_ldiff $opt_required $flagsreceived]]]} {
arg_error "Required option missing for [Get_caller]. missing flags: '$missing' are marked with -optional false - so must be present in full-length form" $argspecs
}
@ -1605,7 +1695,7 @@ tcl::namespace::eval punk::args {
#todo - allow defaults outside of choices/ranges
#check types,ranges,choices
set opts_and_values [tcl::dict::merge $opts $values_dict]
set opts_and_values [tcl::dict::merge $leaders_dict $opts $values_dict]
#set combined_defaults [tcl::dict::merge $val_defaults $opt_defaults] ;#can be no key collisions - we don't allow a value key beginning with dash - opt names must begin with dash
#puts "---opts_and_values:$opts_and_values"
#puts "---arg_info:$arg_info"
@ -1657,11 +1747,17 @@ tcl::namespace::eval punk::args {
set choiceprefix [tcl::dict::get $thisarg -choiceprefix]
set choicerestricted [tcl::dict::get $thisarg -choicerestricted]
set nocase [tcl::dict::get $thisarg -nocase]
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
set dname leaders_dict
}
option {
set dname opts
} else {
}
value {
set dname values_dict
}
}
set idx 0 ;#
#opts/values_dict $argname membmer has been populated with the actual entered choices - which might be prefixes
#assert llength $vlist == llength [dict get $dname $argname]
@ -1819,8 +1915,8 @@ tcl::namespace::eval punk::args {
lappend pass_quick_list_e_check $e_check
}
}
set remaining_e [punk::lib::ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punk::lib::ldiff $vlist_check $pass_quick_list_e_check]
set remaining_e [punklib_ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punklib_ldiff $vlist_check $pass_quick_list_e_check]
}
if {$regexprefail ne ""} {
foreach e $remaining_e e_check $remaining_e_check {
@ -2036,23 +2132,37 @@ tcl::namespace::eval punk::args {
if {$is_strip_ansi} {
set stripped_list [lmap e $vlist_original {punk::ansi::ansistrip $e}] ;#no faster or slower, but more concise than foreach
if {[tcl::dict::get $thisarg -multiple]} {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict $argname $stripped_list
}
option {
tcl::dict::set opts $argname $stripped_list
} else {
}
value {
tcl::dict::set values_dict $argname $stripped_list
}
}
} else {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict [lindex $stripped_list 0]
}
option {
tcl::dict::set opts $argname [lindex $stripped_list 0]
} else {
}
value {
tcl::dict::set values_dict [lindex $stripped_list 0]
}
}
}
}
}
#maintain order of opts $opts values $values as caller may use lassign.
return [tcl::dict::create opts $opts values $values_dict]
set receivednames [list {*}$leadernames_received {*}$flagsreceived {*}$valnames_received]
set received_posns [concat {*}[lmap a $receivednames b [zero_to_n [expr {[llength $receivednames]-1}]] {list $a $b}]] ;#flat zip named with overall posn including opts
return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns]
}
#proc sample1 {p1 args} {
@ -2063,6 +2173,16 @@ tcl::namespace::eval punk::args {
#}
punk::args::definition {
*id punk::args::TEST
*opts -optional 0
-o1 -default 111 -help "opt 1 mandatory"
*opts -optional 1
-o2 -default 222 -help "opt 2 optional"
*values -min 0 -max 1
v -help\
"v1 optional"
}
#*** !doctools

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/fileline-0.1.0.tm

@ -1556,7 +1556,7 @@ namespace eval punk::fileline::lib {
set argd [punk::args::get_dict {
-offset -default 0
} $args]
lassign [dict values $argd] opts remainingargs
lassign [dict values $argd] leaders opts remainingargs
}

81
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm

@ -211,6 +211,9 @@ tcl::namespace::eval punk::lib::compat {
#*** !doctools
#[list_begin definitions]
if {"::lremove" ne [info commands ::lremove]} {
#puts stderr "Warning - no built-in lremove"
interp alias {} lremove {} ::punk::lib::compat::lremove
@ -393,6 +396,80 @@ namespace eval punk::lib {
set has_twapi [expr {![catch {package require twapi}]}]
}
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::lib::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::lib::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
# == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
# Maintenance - This is the primary source for tm_version... functions
# - certain packages script require these but without package dependency
@ -2894,7 +2971,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
-joinchar -default \n
*values -min 1 -max 1
} $args]] opts values
} $args]] leaders opts values
puts "opts:$opts"
puts "values:$values"
return [join [tcl::dict::get $values 0] [tcl::dict::get $opts -joinchar]]
@ -2936,7 +3013,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
*opts -any 1
-block -default {}
} $args]] opts valuedict
} $args]] leaderdict opts valuedict
tailcall linelist {*}$opts {*}[tcl::dict::values $valuedict]
}

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm

@ -153,7 +153,7 @@ namespace eval punk::mix::commandset::module {
module -type string
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set module [dict get $values module]
#set opts [dict merge $defaults $args]

7
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm

@ -643,7 +643,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values_dict
lassign [dict values $argd] leaders opts values_dict
set opt_stripbase [dict get $opts -stripbase]
set opt_formatsizes [dict get $opts -formatsizes]
@ -726,6 +726,7 @@ tcl::namespace::eval punk::nav::fs {
# -searchbase is always passed through - and is only used to construct a location path if a relative searchspec was supplied
proc dirfiles_dict {args} {
set argspecs {
*id punk::nav::fs::dirfiles_dict
*opts -any 0
-searchbase -default ""
-tailglob -default "\uFFFF"
@ -735,7 +736,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1 -type string
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
#puts stderr "searchspecs: $searchspecs [llength $searchspecs]"
@ -1000,7 +1001,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 1 -max -1 -type dict
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set list_of_dicts [dict values $vals]

40
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm

@ -20,8 +20,8 @@
package require punk::lib
package require punk::args
tcl::namespace::eval ::punk_dynamic::ns {
tcl::namespace::eval ::punk::ns::evaluator {
#eval-_NS_xxx_NS_etc procs
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -29,6 +29,12 @@ tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nsimport_noclobber corp
catch {
package require debug
debug define punk.ns.compile
#debug on punk.ns.compile
#debug level punk.ns.compile 3
}
#leading colon makes it hard (impossible?) to call directly if not within the namespace
proc ns/ {v {ns_or_glob ""} args} {
@ -200,16 +206,17 @@ tcl::namespace::eval punk::ns {
return $body
}
proc nseval {fqns script} {
#create one proc for each fully qualified namespace to evaluate script
if {![string match ::* $fqns]} {
error "nseval only accepts a fully qualified namespace"
}
set loc [string map {:: _sep_} $fqns]
set loc [string map {:: _NS_} $fqns]
#set cmd ::punk::pipecmds::nseval_$loc
set cmd ::punk_dynamic::ns::eval-$loc
set cmd ::punk::ns::evaluator::eval-$loc
if {$cmd ni [info commands $cmd]} {
append body \n [nseval_script $fqns]
proc $cmd {script} $body
debug.punk.pipe.compile {proc $cmd} 6
debug.punk.ns.compile {proc $cmd} 2
}
tailcall $cmd $script
}
@ -800,14 +807,23 @@ tcl::namespace::eval punk::ns {
set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
#set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set lenlist1 [lmap v [list {*}$children1 ""] {string length $v}]
set chwidest1 [tcl::mathfunc::max {*}$lenlist1]
#set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$children2 ""] {string length $v}]]
#wrap the cmd in [list] (just for the width calc) to get a proper length for what will actually be displayed
set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
#set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest1 [tcl::mathfunc::max {*}[lmap v [list {*}$elements1 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$elements2 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [tcl::mathfunc::max {*}[lmap v [list {*}$elements3 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [tcl::mathfunc::max {*}[lmap v [list {*}$elements4 ""] {string length [list [lindex $v 1]]}]]
set displaylist [list]
set col1 [string repeat " " [expr {$chwidest1 + 8}]]
@ -1875,7 +1891,7 @@ tcl::namespace::eval punk::ns {
*values -min 1 -max 1
sourcepattern -type string -optional 0
}
lassign [dict values [punk::args::get_dict $argspecs $args]] opts values
lassign [dict values [punk::args::get_dict $argspecs $args]] leaders opts values
set sourcepattern [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm

@ -662,7 +662,7 @@ namespace eval punk::path {
*values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1
} $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set tailglobs [dict values $values]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]

36
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm

@ -13,12 +13,24 @@
tcl::namespace::eval shellfilter::log {
variable allow_adhoc_tags 1
variable open_logs [tcl::dict::create]
variable is_enabled 0
proc disable {} {
variable is_enabled
set is_enabled 0
proc ::shellfilter::log::open {tag settingsdict} {}
proc ::shellfilter::log::write {tag msg} {}
proc ::shellfilter::log::write_sync {tag msg} {}
proc ::shellfilter::log::close {tag} {}
}
proc enable {} {
variable is_enabled
set is_enabled 1
#'tag' is an identifier for the log source.
# each tag will use it's own thread to write to the configured log target
proc open {tag {settingsdict {}}} {
proc ::shellfilter::log::open {tag {settingsdict {}}} {
upvar ::shellfilter::sources sourcelist
package require shellthread
if {![dict exists $settingsdict -tag]} {
tcl::dict::set settingsdict -tag $tag
} else {
@ -36,7 +48,7 @@ tcl::namespace::eval shellfilter::log {
#puts stderr "shellfilter::log::open this_threadid: [thread::id] tag: $tag worker_tid: $worker_tid"
return $worker_tid
}
proc write {tag msg} {
proc ::shellfilter::log::write {tag msg} {
upvar ::shellfilter::sources sourcelist
variable allow_adhoc_tags
if {!$allow_adhoc_tags} {
@ -47,14 +59,16 @@ tcl::namespace::eval shellfilter::log {
shellthread::manager::write_log $tag $msg
}
#write_sync - synchronous processing with logging thread, slower but potentially useful for debugging/testing or forcing delay til log written
proc write_sync {tag msg} {
proc ::shellfilter::log::write_sync {tag msg} {
shellthread::manager::write_log $tag $msg -async 0
}
proc close {tag} {
proc ::shellfilter::log::close {tag} {
#shellthread::manager::close_worker $tag
shellthread::manager::unsubscribe [list $tag]; #workertid will be added back to free list if no tags remain subscribed
}
}
#review
#configure whether we can call shellfilter::log::write without having called open first
proc require_open {{is_open_required {}}} {
@ -73,6 +87,12 @@ tcl::namespace::eval shellfilter::log {
}
}
}
if {[catch {package require shellthread}]} {
shellfilter::log::disable
} else {
shellfilter::log::enable
}
}
namespace eval shellfilter::pipe {
#write channel for program. workerthread reads other end of fifo2 and writes data somewhere
@ -1594,7 +1614,13 @@ namespace eval shellfilter::stack {
set tag "SHELLFILTER::STACK"
#JMN - load from config
#::shellfilter::log::open $tag {-syslog 127.0.0.1:514}
if {[catch {
::shellfilter::log::open $tag {-syslog ""}
} err]} {
#e.g safebase interp can't load required modules such as shellthread (or Thread)
puts stderr "shellfilter::show_pipeline cannot open log"
return
}
::shellfilter::log::write $tag "transform stack for $pipename $args"
foreach tf $stack {
::shellfilter::log::write $tag " $tf"

15
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.2.tm

@ -62,7 +62,14 @@ catch {package require patternpunk}
package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
if {[catch {
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
} errM]} {
#catch this too in case stderr not available
catch {
puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
}
}
package require textutil
@ -5182,7 +5189,7 @@ tcl::namespace::eval textblock {
lassign [punk::args::get_dict {
-ansiresets -default 1 -type integer
blocks -type string -multiple 1
} $args] _o opts _v values
} $args] _l leaders _o opts _v values
set blocks [tcl::dict::get $values blocks]
set idx 0
@ -5844,7 +5851,7 @@ tcl::namespace::eval textblock {
*values -min 1
frametype -choices "<ftlist>" -choiceprefix 0 -choicerestricted 0 -type dict\
-help "name from the predefined frametypes or an adhoc dictionary."
memberglob -type globstring -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
memberglob -type globstring -optional 1 -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
corner noncorner top bottom vertical horizontal left right
hl hlt hlb vsl vll vlr tlc trc blc brc hltj hlbj vllj vlrj
}\
@ -8110,7 +8117,7 @@ tcl::namespace::eval textblock {
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
*values -min 0 -max 1
size -default 1 -type integer
}
proc gcross {args} {

74
src/vfs/_vfscommon.vfs/modules/punk-0.1.tm

@ -6976,7 +6976,7 @@ namespace eval punk {
-punctchars -default { [list \{ \} \" \\ - _ + = . > , < ' : \; ` ~ ! @ # \$ % ^ & * \[ \] ( ) | / ?] }
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
# -- --- --- --- --- ---
@ -7560,79 +7560,13 @@ namespace eval punk {
#this hides windows cmd's mode command - probably no big deal - anyone who needs it will know how to exec it.
interp alias {} mode {} punk::mode
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
tailcall punk::lib::aliases $glob
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
tailcall punk::lib::alias $aliasorglob {*}$args
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
#pipeline-toys - put in lib/scriptlib?
##geometric mean
@ -7669,8 +7603,6 @@ namespace eval punk {
interp alias {} help {} punk::help
interp alias {} aliases {} punk::aliases
interp alias {} alias {} punk::alias
interp alias {} treemore {} punk::xmore tree
interp alias {} tmhere {} .= pwd |path> {::tcl::tm::add {*}$data; set path} |> inspect -label added_to_module_path <0/#|

36
src/vfs/_vfscommon.vfs/modules/punk/aliascore-0.1.0.tm

@ -105,6 +105,8 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
list_as_lines ::punk::lib::list_as_lines\
lines_as_list ::punk::lib::lines_as_list\
@ -155,34 +157,48 @@ tcl::namespace::eval punk::aliascore {
set opts [dict merge $defaults $args]
set opt_force [dict get $opts -force]
#we never override existing aliases to ::repl::interp* even if -force = 1
#(these are our safebase aliases)
set ignore_pattern "::repl::interp*"
set ignore_aliases [list]
variable aliases
if {!$opt_force} {
set existing [list]
set conflicts [list]
foreach {a cmd} $aliases {
if {[tcl::info::commands ::$a] ne ""} {
lappend existing $a
if {[llength $cmd] > 1} {
#use alias mechanism
set existing_target [interp alias "" $a]
set existing_alias [interp alias "" $a]
if {$existing_alias ne ""} {
set existing_target $existing_alias
if {[string match $ignore_pattern $existing_target]} {
#don't consider it a conflict - will use ignore_aliases to exclude it below
lappend ignore_aliases $a
continue
}
} else {
#using namespace import
#check origin
set existing_target [tcl::namespace::origin $cmd]
if {[catch {tcl::namespace::origin $a} existing_command]} {
set existing_command ""
}
set existing_target $existing_command
}
if {$existing_target ne $cmd} {
#command exists in global ns but doesn't match our defined aliases/imports
lappend conflicts $a
}
}
}
if {[llength $conflicts]} {
if {!$opt_force && [llength $conflicts]} {
error "punk::aliascore::init declined to create any aliases or imports because -force == 0 and conflicts found:$conflicts"
}
}
set tempns ::temp_[info cmdcount] ;#temp ns for renames
dict for {a cmd} $aliases {
#puts "aliascore $a -> $cmd"
if {$a in $ignore_aliases} {
continue
}
if {[llength $cmd] > 1} {
interp alias {} $a {} {*}$cmd
} else {
@ -205,7 +221,7 @@ tcl::namespace::eval punk::aliascore {
}
}
#tcl::namespace::delete $tempns
return [dict keys $aliases]
return [dict create aliases [dict keys $aliases] unchanged $ignore_aliases changed $conflicts]
}

576
src/vfs/_vfscommon.vfs/modules/punk/args-0.1.0.tm

@ -58,7 +58,7 @@
# #setting -type none indicates a flag that doesn't take a value (solo flag)
# -nocomplain -type none
# *values -min 1 -max -1
# } $args]] opts values
# } $args]] leaders opts values
#
# puts "translation is [dict get $opts -translation]"
# foreach f [dict values $values] {
@ -68,7 +68,7 @@
#}]
#[para]The lines beginning with * are optional in most cases and can be used to set defaults and some extra controls
#[para] - the above example would work just fine with only the -<optionname> lines, but would allow zero filenames to be supplied as no -min value is set for *values
#[para]valid * lines being with *proc *opts *values
#[para]valid * lines being with *proc *leaders *opts *values
#[para]lines beginning with a dash define options - a name can optionally be given to each trailing positional argument.
#[para]If no names are defined for positional arguments, they will end up in the values key of the dict with numerical keys starting at zero.
#[para]e.g the result from the punk::args call above may be something like:
@ -299,6 +299,22 @@ tcl::namespace::eval punk::args {
}
return $result
}
proc flatzip {l1 l2} {
concat {*}[lmap a $l1 b $l2 {list $a $b}]
}
if {[info commands lseq] ne ""} {
#tcl 8.7+ lseq significantly faster, especially for larger ranges
#The internal rep can be an 'arithseries' with no string representation
proc zero_to_n {n} {
lseq 0 $n
}
} else {
proc zero_to_n {n} {
lsearch -all [lrepeat $n 0] *
}
}
#todo? -synonym/alias ? (applies to opts only not values)
#e.g -background -aliases {-bg} -default White
@ -309,8 +325,8 @@ tcl::namespace::eval punk::args {
variable argspec_cache
#variable argspecs ;#REVIEW!!
variable argspec_ids
variable initial_optspec_defaults
variable initial_valspec_defaults
#variable initial_optspec_defaults
#variable initial_valspec_defaults
#ideally we would use a fast hash algorithm to produce a short key with low collision probability.
#something like md5 would be ok (this is non cryptographic) - but md5 uses open and isn't usable by default in a safe interp.
#review - check if there is a built-into-tcl way to do this quickly
@ -335,6 +351,19 @@ tcl::namespace::eval punk::args {
-regexprepass {}\
-validationtransform {}\
]
set leaderspec_defaults [tcl::dict::create\
-type string\
-optional 0\
-allow_ansi 1\
-validate_ansistripped 0\
-strip_ansi 0\
-nocase 0\
-choiceprefix 1\
-choicerestricted 1\
-multiple 0\
-regexprepass {}\
-validationtransform {}\
]
set valspec_defaults [tcl::dict::create\
-type string\
-optional 0\
@ -357,15 +386,20 @@ tcl::namespace::eval punk::args {
#default -allow_ansi to 1 and -validate_ansistripped to 0 and -strip_ansi 0 - it takes time to strip ansi
#todo - detect if anything in the spec uses -allow_ansi 0, -validate_ansistripped 1 or -strip_ansi 1 and set a flag indicating if punk::ansi::ta::detect should be run on the argslist
set leader_required [list]
set opt_required [list]
set val_required [list]
set arg_info [tcl::dict::create]
set arg_checks [tcl::dict::create]
set opt_defaults [tcl::dict::create]
set opt_names [list] ;#defined opts
set leader_defaults [tcl::dict::create]
set val_defaults [tcl::dict::create]
set opt_solos [list]
#first process dashed and non-dashed argspecs without regard to whether non-dashed are at the beginning or end
set leader_names [list]
set val_names [list]
set records [list]
@ -439,10 +473,14 @@ tcl::namespace::eval punk::args {
}
set proc_info {}
set id_info {} ;#e.g -children <list> ??
set leader_min 0
set leader_max 0 ;#default 0 but if leader_min is set switches to -1 for no limit
#(common case of no leaders specified)
set opt_any 0
set val_min 0
set val_max -1 ;#-1 for no limit
set spec_id ""
set argspace "leaders" ;#leaders -> options -> values
foreach ln $records {
set trimln [tcl::string::trim $ln]
switch -- [tcl::string::index $trimln 0] {
@ -477,6 +515,10 @@ tcl::namespace::eval punk::args {
set proc_info $starspecs
}
opts {
if {$argspace eq "values"} {
error "punk::args::definition - *opts declaration must come before *values - received '$linespecs'"
}
set argspace "options"
foreach {k v} $starspecs {
switch -- $k {
-any -
@ -537,7 +579,86 @@ tcl::namespace::eval punk::args {
}
}
}
leaders {
if {$argspace in [list options values]} {
error "punk::args::definition - *leaders declaration must come before all options and values"
}
foreach {k v} $starspecs {
switch -- $k {
-min -
-minvalues {
if {$v < 0} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
}
set leader_min $v
if {$leader_max == 0} {
set leader_max -1
}
}
-max -
-maxvalues {
if {$v < -1} {
error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
}
set leader_max $v
}
-minlen - -maxlen - -range - -choices - -choicelabels - -choiceprefix - -choicerestricted - -nocase {
#review - only apply to certain types?
tcl::dict::set leaderspec_defaults $k $v
}
-nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels {
if {$v} {
tcl::dict::unset leaderspec_defaults $k
}
}
-type {
switch -- $v {
int - integer {
set v int
}
char - character {
set v char
}
bool - boolean {
set v bool
}
dict - dictionary {
set v dict
}
list {
}
default {
#todo - disallow unknown types unless prefixed with custom-
}
}
tcl::dict::set leaderspec_defaults $k $v
}
-optional -
-allow_ansi -
-validate_ansistripped -
-strip_ansi -
-regexprepass -
-regexprefail -
-validationtransform -
-multiple {
tcl::dict::set leaderspec_defaults $k $v
}
default {
set known { -min -minvalues -max -maxvalues\
-minlen -maxlen -range -choices -choicelabels -choiceprefix -choicerestricted -nocase\
-nominlen -nomaxlen -norange -nochoices -nochoicelabels\
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\
}
error "punk::args::definition - unrecognised key '$k' in *leaders line. Known keys: $known"
}
}
}
}
values {
set argspace "values"
foreach {k v} $starspecs {
switch -- $k {
-min -
@ -610,11 +731,16 @@ tcl::namespace::eval punk::args {
}
default {
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *leaders *opts or *values - use **name if paramname needs to be *name"
}
}
continue
} elseif {$firstchar eq "-"} {
if {$argspace eq "leaders"} {
set argspace "options"
} elseif {$argspace eq "values"} {
error "punk::args::definition - invalid placement of line '$ln' - must come before *values"
}
set argspecs $linespecs
tcl::dict::set argspecs -ARGTYPE option
lappend opt_names $argname
@ -625,8 +751,16 @@ tcl::namespace::eval punk::args {
set argname [tcl::string::range $argname 1 end]
}
set argspecs $linespecs
if {$argspace eq "leaders"} {
tcl::dict::set argspecs -ARGTYPE leader
lappend leader_names $argname
if {$leader_max == 0} {
set leader_max [llength $leader_names]
}
} else {
tcl::dict::set argspecs -ARGTYPE value
lappend val_names $argname
}
set is_opt 0
}
#assert - we only get here if it is a value or flag specification line.
@ -634,7 +768,11 @@ tcl::namespace::eval punk::args {
if {$is_opt} {
set spec_merged $optspec_defaults
} else {
if {$argspace eq "values"} {
set spec_merged $valspec_defaults
} else {
set spec_merged $leaderspec_defaults
}
}
foreach {spec specval} $argspecs {
#literal-key switch - bytecompiled to jumpTable
@ -726,23 +864,37 @@ tcl::namespace::eval punk::args {
if {![tcl::dict::get $argspecs -optional] && ![tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
lappend opt_required $argname
} else {
if {$argspace eq "leaders"} {
lappend leader_required $argname
} else {
lappend val_required $argname
}
}
}
if {[tcl::dict::exists $argspecs -default]} {
if {$is_opt} {
tcl::dict::set opt_defaults $argname [tcl::dict::get $argspecs -default]
} else {
if {$argspace eq "leaders"} {
tcl::dict::set leader_defaults $argname [tcl::dict::get $argspecs -default]
} else {
tcl::dict::set val_defaults $argname [tcl::dict::get $argspecs -default]
}
}
}
}
# REVIEW
foreach leadername [lrange $leader_names 0 end] {
if {[tcl::dict::get $arg_info $leadername -multiple]} {
error "bad key -multiple on argument spec for leader '$valname'. Only the last value argument specification can be marked -multiple"
}
}
#confirm any valnames before last don't have -multiple key
foreach valname [lrange $val_names 0 end-1] {
if {[tcl::dict::get $arg_info $valname -multiple]} {
error "bad key -multiple on argument spec for '$valname'. Only the last value argument specification can be marked -multiple"
error "bad key -multiple on argument spec for value '$valname'. Only the last value argument specification can be marked -multiple"
}
}
if {$spec_id eq "" || [tcl::string::tolower $spec_id] eq "auto"} {
@ -750,7 +902,11 @@ tcl::namespace::eval punk::args {
set spec_id "autoid_[incr id_counter]"
}
#todo - document that ambiguities in API are likely if both *leaders and *values used
#todo - do some checks for obvious bad definitions involving a mix of *leaders and *values (e.g with optional options)
set leader_checks_defaults [tcl::dict::remove $leaderspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set opt_checks_defaults [tcl::dict::remove $optspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
set val_checks_defaults [tcl::dict::remove $valspec_defaults -type -default -multiple -strip_ansi -validate_ansistripped -allow_ansi] ;#leave things like -range -minlen
@ -758,6 +914,13 @@ tcl::namespace::eval punk::args {
id $spec_id\
arg_info $arg_info\
arg_checks $arg_checks\
leader_defaults $leader_defaults\
leader_required $leader_required\
leader_names $leader_names\
leader_min $leader_min\
leader_max $leader_max\
leaderspec_defaults $leaderspec_defaults\
leader_checks_defaults $leader_checks_defaults\
opt_defaults $opt_defaults\
opt_required $opt_required\
opt_names $opt_names\
@ -782,13 +945,33 @@ tcl::namespace::eval punk::args {
return $result
}
proc get_spec {id} {
proc get_spec {id {patternlist *}} {
variable argspec_ids
if {[tcl::dict::exists $argspec_ids $id]} {
if {$patternlist eq "*"} {
return [tcl::dict::get $argspec_ids $id]
} else {
set spec [tcl::dict::get $argspec_ids $id]
set result ""
set specdict [definition $spec]
set arg_info [dict get $specdict arg_info]
foreach pat $patternlist {
set matches [dict keys $arg_info $pat]
foreach m $matches {
set def [dict get $arg_info $m]
set def [dict remove $def -ARGTYPE]
append result \n "$m $def"
}
}
return $result
}
}
return
}
#proc get_spec_leaders ??
#proc get_spec_opts ??
#proc get_spec_values ??
proc get_spec_ids {{match *}} {
variable argspec_ids
return [tcl::dict::keys $argspec_ids $match]
@ -876,17 +1059,17 @@ tcl::namespace::eval punk::args {
$t add_column -headers $blank_header_col
$t add_column -headers $blank_header_col
if {"$procname$prochelp" eq ""} {
$t configure_header 0 -values {Arg Type Default Multiple Help}
$t configure_header 0 -values {Arg Type Default Multi Help}
} elseif {$procname eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} elseif {$prochelp eq ""} {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -values {Arg Type Default Multiple Help}
$t configure_header 1 -values {Arg Type Default Multi Help}
} else {
$t configure_header 0 -colspans {1 4 0 0 0} -values [list PROC/METHOD: $procname_display]
$t configure_header 1 -colspans {1 4 0 0 0} -values [list Description: $prochelp_display]
$t configure_header 2 -values {Arg Type Default Multiple Help}
$t configure_header 2 -values {Arg Type Default Multi Help}
}
@ -894,7 +1077,8 @@ tcl::namespace::eval punk::args {
#set A_DEFAULT [a+ brightwhite Brightgreen]
set A_DEFAULT ""
set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a]
set greencheck [a+ brightgreen]\u2713[a] ;#green tick
set soloflag [a+ brightcyan]\u2690[a] ;#flag - may be replacement char in old dos prompt (?)
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
@ -930,20 +1114,21 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names
}
}
set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names [dict get $spec_dict leader_names]
set trailing_val_names [dict get $spec_dict val_names]
#dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
# if {![string match -* $argname]} {
# lappend leading_val_names [lpop trailing_val_names 0]
# } else {
# break
# }
#}
#if {![llength $leading_val_names] && ![llength $opt_names]} {
# #all vals were actually trailing - no opts
# set trailing_val_names $leading_val_names
# set leading_val_names {}
#}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
@ -954,7 +1139,7 @@ tcl::namespace::eval punk::args {
foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} {
set default $A_DEFAULT[dict get $arginfo -default]$RST
set default "'$A_DEFAULT[dict get $arginfo -default]$RST'"
} else {
set default ""
}
@ -1022,7 +1207,14 @@ tcl::namespace::eval punk::args {
} else {
set multiple ""
}
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {[::punk::args::Dict_getdef $arginfo -optional 0] == 1 || [dict exists $arginfo -default]} {
set argshow "?${argshow}?"
}
set typeshow [dict get $arginfo -type]
if {$typeshow eq "none"} {
set typeshow "$typeshow $soloflag"
}
$t add_row [list $argshow $typeshow $default $multiple $help]
if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
}
@ -1087,7 +1279,7 @@ tcl::namespace::eval punk::args {
#[para]argumentname -key val -ky2 val2...
#[para]where the valid keys for each option specification are: -default -type -range -choices
#[para]comment lines begining with # are ignored and can be placed anywhere except within a multiline value where it would become part of that value
#[para]lines beginning with *proc *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]lines beginning with *proc *leaders *opts or *values also take -key val pairs and can be used to set defaults and control settings.
#[para]*opts or *values lines can appear multiple times with defaults affecting flags/values that follow.
#[arg_def list rawargs]
#[para] This is a list of the arguments to parse. Usually it will be the $args value from the containing proc,
@ -1162,17 +1354,55 @@ tcl::namespace::eval punk::args {
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
#dict for {a info} $arg_info {
# #todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
# if {![string match -* $a]} {
# #lappend pre_values [lpop rawargs 0]
# if {[catch {lpop rawargs 0} val]} {
# break
# } else {
# lappend pre_values $val
# }
# } else {
# break
# }
#}
set argnames [dict keys $arg_info]
set optnames [lsearch -all -inline $argnames -*]
set ridx 0
set rawargs_copy $rawargs
if {$leader_max != 0} {
foreach r $rawargs_copy {
if {$leader_max != -1 && $ridx > $leader_max-1} {
break
}
if {[string match -* $r]} {
if {$r eq "--"} {
break
}
set matchopt [::tcl::prefix::match -error {} $optnames $r]
if {$matchopt ne ""} {
#flaglike matches a known flag - don't treat as leader
break
}
if {![string match -* [lindex $argnames $ridx]]} {
#there is a named leading positional for this position
lappend pre_values [lpop rawargs 0]
incr ridx
continue
} else {
break
}
}
lappend pre_values [lpop rawargs 0]
incr ridx
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} {
set leaders [list]
set arglist {}
set post_values {}
#val_min, val_max
@ -1326,199 +1556,40 @@ tcl::namespace::eval punk::args {
break
}
}
set values [list {*}$pre_values {*}$post_values]
#set values [list {*}$pre_values {*}$post_values]
set leaders $pre_values
set values $post_values
} else {
set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set leaders $pre_values
set values $rawargs
#set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list]
}
#puts stderr "--> arglist: $arglist"
#puts stderr "--> values: $values"
}
if {$id eq "jtest"} {
if {[set eopts [lsearch -exact $rawargs "--"]] >= 0} {
lappend flagsreceived --
set values [lrange $rawargs $eopts+1 end]
set arglist [lrange $rawargs 0 $eopts-1]
set maxidx [expr {[llength $arglist]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $arglist $i]
if {![tcl::string::match -* $a]} {
#we can't treat as first positional arg - as it comes before the eopt indicator --
arg_error "punk::args::get_dict bad options for [Get_caller]. Expected flag (leading -) at position $i got:$rawargs" $argspecs
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $arglist $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $arglist $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
} else {
tcl::dict::set opts $a $newval
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
} else {
if {[lsearch $rawargs -*] >= 0} {
#no -- end of opts indicator
#to support option values with leading dash e.g -offset -1 , we can't just use the last flagindex to determine start of positional args.
#we break on first non-flag looking argument that isn't in an option's value position and use that index as the division.
#The caller should use -- if the first positional arg is likely or has the potential to start with a dash.
set maxidx [expr {[llength $rawargs]-1}]
for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $rawargs $i]
#we can automatically rule out arguments containing whitespace from the set of simple flags beginning with a dash
#This helps for example when first value is a dict or list in which the first item happens to begin with a dash
#explicit -- still safer in many cases, but this is a reasonable and fast enough test
if {![tcl::string::match -* $a] || [regexp {\s+} $a]} {
#assume beginning of positional args
incr i -1
set positionalidx 0 ;#index for unnamed positionals (both leaders and values)
set ldridx 0
set leadernames_received [list]
set leaders_dict $leader_defaults
set num_leaders [llength $leaders]
foreach leadername $leader_names ldr $leaders {
if {$ldridx+1 > $num_leaders} {
break
}
if {![catch {tcl::prefix match -message "options for %caller%. Unexpected option" $opt_names $a } fullopt]} {
if {[tcl::dict::get $arg_info $fullopt -type] ne "none"} {
#non-solo
set flagval [lindex $rawargs $i+1]
if {[dict get $arg_info $fullopt -multiple]} {
#don't lappend to default - we need to replace if there is a default
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
} else {
tcl::dict::set opts $fullopt $flagval
}
#incr i to skip flagval
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last option $fullopt at index [expr {$i-1}] which is not marked with -type none" $argspecs $fullopt
}
} else {
#type none (solo-flag)
if {[tcl::dict::get $arg_info $fullopt -multiple]} {
if {[tcl::dict::get $opts $fullopt] == 0} {
#review - what if default at time opt was specified is not zero? we will end up with more items in the list than the number of times the flag was specified
tcl::dict::set opts $fullopt 1
} else {
tcl::dict::lappend opts $fullopt 1
}
} else {
tcl::dict::set opts $fullopt 1
}
}
lappend flagsreceived $fullopt ;#dups ok
} else {
if {$opt_any} {
set newval [lindex $rawargs $i+1]
#opt was unspecified but is allowed due to *opt -any 1 - 'adhoc/passthrough' option
tcl::dict::set arg_info $a $optspec_defaults ;#use default settings for unspecified opt
tcl::dict::set arg_checks $a $opt_checks_defaults
if {[tcl::dict::get $arg_info $a -type] ne "none"} {
if {[tcl::dict::get $arg_info $a -multiple]} {
tcl::dict::lappend opts $a $newval
if {$leadername ne ""} {
tcl::dict::set leaders_dict $leadername $ldr
lappend leadernames_received $leadername
} else {
tcl::dict::set opts $a $newval
tcl::dict::set leaders_dict $positionalidx $ldr
tcl::dict::set arg_info $positionalidx $leaderspec_defaults
tcl::dict::set arg_checks $positionalidx $leader_checks_defaults
lappend leadernames_received $positionalidx
}
lappend flagsreceived $a ;#adhoc flag as supplied
if {[incr i] > $maxidx} {
arg_error "punk::args::get_dict bad options for [Get_caller]. No value supplied for last adhoc option $a at index [expr {$i-1}] which is not marked with -type none" $argspecs $a
incr ldridx
incr positionalidx
}
} else {
#review -we can't provide a way to allow unspecified -type none flags through reliably/unambiguously unless all remaining unspecified options are -type none
if {[tcl::dict::get $arg_info $a -multiple]} {
if {![tcl::dict::exists $opts $a]} {
tcl::dict::set opts $a 1
} else {
tcl::dict::lappend opts $a 1
}
} else {
tcl::dict::set opts $a 1
}
}
} else {
#delay Get_caller so only called in the unhappy path
set errmsg [tcl::string::map [list %caller% [Get_caller]] $fullopt]
arg_error $errmsg $argspecs $fullopt
}
}
}
set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end]
#puts "$i--->arglist:$arglist"
#puts "$i--->values:$values"
} else {
set values $rawargs ;#no -flags detected
set arglist [list]
}
}
}
set validx 0
set in_multiple ""
@ -1547,13 +1618,29 @@ tcl::namespace::eval punk::args {
tcl::dict::lappend values_dict $in_multiple $val
#name already seen
} else {
tcl::dict::set values_dict $validx $val
tcl::dict::set arg_info $validx $valspec_defaults
tcl::dict::set arg_checks $validx $val_checks_defaults
lappend valnames_received $validx
tcl::dict::set values_dict $positionalidx $val
tcl::dict::set arg_info $positionalidx $valspec_defaults
tcl::dict::set arg_checks $positionalidx $val_checks_defaults
lappend valnames_received $positionalidx
}
}
incr validx
incr positionalidx
}
if {$leader_max == -1} {
#only check min
if {$num_leaders < $leader_min} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected at least $leader_min" $argspecs
}
} else {
if {$num_leaders < $leader_min || $num_leaders > $leader_max} {
if {$leader_min == $leader_max} {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected exactly $leader_min" $argspecs
} else {
arg_error "bad number of leading values for [Get_caller]. Got $num_leaders leaders. Expected between $leader_min and $leader_max inclusive" $argspecs
}
}
}
if {$val_max == -1} {
@ -1592,6 +1679,9 @@ tcl::namespace::eval punk::args {
# error "Required value missing for [Get_caller]. missing values $missing marked with -optional false - so must be present"
#}
#for now (2024-06) punk::lib::ldiff is a better compromise across normal/safe interps e.g 0.7/0.8us
if {[llength [set missing [punklib_ldiff $leader_required $leadernames_received]]]} {
arg_error "Required leader missing for [Get_caller]. missing values: '$missing' marked with -optional false - so must be present" $argspecs
}
if {[llength [set missing [punklib_ldiff $opt_required $flagsreceived]]]} {
arg_error "Required option missing for [Get_caller]. missing flags: '$missing' are marked with -optional false - so must be present in full-length form" $argspecs
}
@ -1605,7 +1695,7 @@ tcl::namespace::eval punk::args {
#todo - allow defaults outside of choices/ranges
#check types,ranges,choices
set opts_and_values [tcl::dict::merge $opts $values_dict]
set opts_and_values [tcl::dict::merge $leaders_dict $opts $values_dict]
#set combined_defaults [tcl::dict::merge $val_defaults $opt_defaults] ;#can be no key collisions - we don't allow a value key beginning with dash - opt names must begin with dash
#puts "---opts_and_values:$opts_and_values"
#puts "---arg_info:$arg_info"
@ -1657,11 +1747,17 @@ tcl::namespace::eval punk::args {
set choiceprefix [tcl::dict::get $thisarg -choiceprefix]
set choicerestricted [tcl::dict::get $thisarg -choicerestricted]
set nocase [tcl::dict::get $thisarg -nocase]
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
set dname leaders_dict
}
option {
set dname opts
} else {
}
value {
set dname values_dict
}
}
set idx 0 ;#
#opts/values_dict $argname membmer has been populated with the actual entered choices - which might be prefixes
#assert llength $vlist == llength [dict get $dname $argname]
@ -1819,8 +1915,8 @@ tcl::namespace::eval punk::args {
lappend pass_quick_list_e_check $e_check
}
}
set remaining_e [punk::lib::ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punk::lib::ldiff $vlist_check $pass_quick_list_e_check]
set remaining_e [punklib_ldiff $vlist $pass_quick_list_e]
set remaining_e_check [punklib_ldiff $vlist_check $pass_quick_list_e_check]
}
if {$regexprefail ne ""} {
foreach e $remaining_e e_check $remaining_e_check {
@ -2036,23 +2132,37 @@ tcl::namespace::eval punk::args {
if {$is_strip_ansi} {
set stripped_list [lmap e $vlist_original {punk::ansi::ansistrip $e}] ;#no faster or slower, but more concise than foreach
if {[tcl::dict::get $thisarg -multiple]} {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict $argname $stripped_list
}
option {
tcl::dict::set opts $argname $stripped_list
} else {
}
value {
tcl::dict::set values_dict $argname $stripped_list
}
}
} else {
if {[tcl::dict::get $thisarg -ARGTYPE] eq "option"} {
switch -- [tcl::dict::get $thisarg -ARGTYPE] {
leader {
tcl::dict::set leaders_dict [lindex $stripped_list 0]
}
option {
tcl::dict::set opts $argname [lindex $stripped_list 0]
} else {
}
value {
tcl::dict::set values_dict [lindex $stripped_list 0]
}
}
}
}
}
#maintain order of opts $opts values $values as caller may use lassign.
return [tcl::dict::create opts $opts values $values_dict]
set receivednames [list {*}$leadernames_received {*}$flagsreceived {*}$valnames_received]
set received_posns [concat {*}[lmap a $receivednames b [zero_to_n [expr {[llength $receivednames]-1}]] {list $a $b}]] ;#flat zip named with overall posn including opts
return [tcl::dict::create leaders $leaders_dict opts $opts values $values_dict received $received_posns]
}
#proc sample1 {p1 args} {
@ -2063,6 +2173,16 @@ tcl::namespace::eval punk::args {
#}
punk::args::definition {
*id punk::args::TEST
*opts -optional 0
-o1 -default 111 -help "opt 1 mandatory"
*opts -optional 1
-o2 -default 222 -help "opt 2 optional"
*values -min 0 -max 1
v -help\
"v1 optional"
}
#*** !doctools

2
src/vfs/_vfscommon.vfs/modules/punk/fileline-0.1.0.tm

@ -1556,7 +1556,7 @@ namespace eval punk::fileline::lib {
set argd [punk::args::get_dict {
-offset -default 0
} $args]
lassign [dict values $argd] opts remainingargs
lassign [dict values $argd] leaders opts remainingargs
}

81
src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.1.tm

@ -211,6 +211,9 @@ tcl::namespace::eval punk::lib::compat {
#*** !doctools
#[list_begin definitions]
if {"::lremove" ne [info commands ::lremove]} {
#puts stderr "Warning - no built-in lremove"
interp alias {} lremove {} ::punk::lib::compat::lremove
@ -393,6 +396,80 @@ namespace eval punk::lib {
set has_twapi [expr {![catch {package require twapi}]}]
}
#NOTE: an alias may match in a namespace - but not have a corresponding command that matches that name (alias renamed)
proc aliases {{glob *}} {
set ns [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
set ns_mapped [string map {:: \uFFFF} $ns]
#puts stderr "aliases ns: $ns_mapped"
set segments [split $ns_mapped \uFFFF] ;#include empty string before leading ::
if {![string length [lindex $segments end]]} {
#special case for :: only include leading segment rather thatn {} {}
set segments [lrange $segments 0 end-1]
}
set segcount [llength $segments] ;#only match number of segments matching current ns
set all_aliases [interp aliases {}]
set matched [list]
foreach a $all_aliases {
#normalize with leading ::
if {![string match ::* $a]} {
set abs ::$a
} else {
set abs $a
}
set asegs [split [string map {:: \uFFFF} $abs] \uFFFF]
set acount [llength $asegs]
#puts "alias $abs acount:$acount asegs:$asegs segcount:$segcount segments: $segments"
if {[expr {$acount - 1}] == $segcount} {
if {[lrange $asegs 0 end-1] eq $segments} {
if {[string match $glob [lindex $asegs end]]} {
#report this alias in the current namespace - even though there may be no matching command
lappend matched $a ;#add raw alias token which may or may not have leading ::
}
}
}
}
#set matched_abs [lsearch -all -inline $all_aliases $glob]
return $matched
}
proc alias {{aliasorglob ""} args} {
set nsthis [uplevel 1 {::namespace current}] ;#must use :: - we can find ourselves in a namespace with a different 'namespace' command
if {[llength $args]} {
if {$aliasorglob in [interp aliases ""]} {
set existing [interp alias "" $aliasorglob]
puts stderr "Overwriting existing alias $aliasorglob -> $existing with $aliasorglob -> $args (in current session only)"
}
if {([llength $args] ==1) && [string trim [lindex $args 0]] eq ""} {
#use empty string/whitespace as intention to delete alias
return [interp alias "" $aliasorglob ""]
}
return [interp alias "" $aliasorglob "" {*}$args]
} else {
if {![string length $aliasorglob]} {
set aliaslist [punk::lib::aliases]
puts -nonewline stderr $aliaslist
return
}
#we need to first check for exact match of alias that happens to have glob chars i.e the supplied aliasorglob looks like a glob but is actually directly an alias
set target [interp alias "" $aliasorglob]
if {[llength $target]} {
return $target
}
if {([string first "*" $aliasorglob] >= 0) || ([string first "?" $aliasorglob] >= 0)} {
set aliaslist [punk::lib::aliases $aliasorglob]
puts -nonewline stderr $aliaslist
return
}
return [list]
}
}
# == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == ==
# Maintenance - This is the primary source for tm_version... functions
# - certain packages script require these but without package dependency
@ -2894,7 +2971,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
-joinchar -default \n
*values -min 1 -max 1
} $args]] opts values
} $args]] leaders opts values
puts "opts:$opts"
puts "values:$values"
return [join [tcl::dict::get $values 0] [tcl::dict::get $opts -joinchar]]
@ -2936,7 +3013,7 @@ namespace eval punk::lib {
lassign [tcl::dict::values [punk::args::get_dict {
*opts -any 1
-block -default {}
} $args]] opts valuedict
} $args]] leaderdict opts valuedict
tailcall linelist {*}$opts {*}[tcl::dict::values $valuedict]
}

2
src/vfs/_vfscommon.vfs/modules/punk/mix/commandset/module-0.1.0.tm

@ -153,7 +153,7 @@ namespace eval punk::mix::commandset::module {
module -type string
}]
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set module [dict get $values module]
#set opts [dict merge $defaults $args]

7
src/vfs/_vfscommon.vfs/modules/punk/nav/fs-0.1.0.tm

@ -643,7 +643,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts values_dict
lassign [dict values $argd] leaders opts values_dict
set opt_stripbase [dict get $opts -stripbase]
set opt_formatsizes [dict get $opts -formatsizes]
@ -726,6 +726,7 @@ tcl::namespace::eval punk::nav::fs {
# -searchbase is always passed through - and is only used to construct a location path if a relative searchspec was supplied
proc dirfiles_dict {args} {
set argspecs {
*id punk::nav::fs::dirfiles_dict
*opts -any 0
-searchbase -default ""
-tailglob -default "\uFFFF"
@ -735,7 +736,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 0 -max -1 -type string
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set searchspecs [dict values $vals]
#puts stderr "searchspecs: $searchspecs [llength $searchspecs]"
@ -1000,7 +1001,7 @@ tcl::namespace::eval punk::nav::fs {
*values -min 1 -max -1 -type dict
}
set argd [punk::args::get_dict $argspecs $args]
lassign [dict values $argd] opts vals
lassign [dict values $argd] leaders opts vals
set list_of_dicts [dict values $vals]

40
src/vfs/_vfscommon.vfs/modules/punk/ns-0.1.0.tm

@ -20,8 +20,8 @@
package require punk::lib
package require punk::args
tcl::namespace::eval ::punk_dynamic::ns {
tcl::namespace::eval ::punk::ns::evaluator {
#eval-_NS_xxx_NS_etc procs
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -29,6 +29,12 @@ tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nsimport_noclobber corp
catch {
package require debug
debug define punk.ns.compile
#debug on punk.ns.compile
#debug level punk.ns.compile 3
}
#leading colon makes it hard (impossible?) to call directly if not within the namespace
proc ns/ {v {ns_or_glob ""} args} {
@ -200,16 +206,17 @@ tcl::namespace::eval punk::ns {
return $body
}
proc nseval {fqns script} {
#create one proc for each fully qualified namespace to evaluate script
if {![string match ::* $fqns]} {
error "nseval only accepts a fully qualified namespace"
}
set loc [string map {:: _sep_} $fqns]
set loc [string map {:: _NS_} $fqns]
#set cmd ::punk::pipecmds::nseval_$loc
set cmd ::punk_dynamic::ns::eval-$loc
set cmd ::punk::ns::evaluator::eval-$loc
if {$cmd ni [info commands $cmd]} {
append body \n [nseval_script $fqns]
proc $cmd {script} $body
debug.punk.pipe.compile {proc $cmd} 6
debug.punk.ns.compile {proc $cmd} 2
}
tailcall $cmd $script
}
@ -800,14 +807,23 @@ tcl::namespace::eval punk::ns {
set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
#set chwidest1 [pipedata [list {*}$children1 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set lenlist1 [lmap v [list {*}$children1 ""] {string length $v}]
set chwidest1 [tcl::mathfunc::max {*}$lenlist1]
#set chwidest2 [pipedata [list {*}$children2 ""] {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data}]
set chwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$children2 ""] {string length $v}]]
#wrap the cmd in [list] (just for the width calc) to get a proper length for what will actually be displayed
set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
#set cmdwidest1 [pipedata [list {*}$elements1 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest1 [tcl::mathfunc::max {*}[lmap v [list {*}$elements1 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest2 [pipedata [list {*}$elements2 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest2 [tcl::mathfunc::max {*}[lmap v [list {*}$elements2 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest3 [pipedata [list {*}$elements3 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest3 [tcl::mathfunc::max {*}[lmap v [list {*}$elements3 ""] {string length [list [lindex $v 1]]}]]
#set cmdwidest4 [pipedata [list {*}$elements4 ""] {lmap v $data {string length [list [lindex $v 1]]}} {tcl::mathfunc::max {*}$data}]
set cmdwidest4 [tcl::mathfunc::max {*}[lmap v [list {*}$elements4 ""] {string length [list [lindex $v 1]]}]]
set displaylist [list]
set col1 [string repeat " " [expr {$chwidest1 + 8}]]
@ -1875,7 +1891,7 @@ tcl::namespace::eval punk::ns {
*values -min 1 -max 1
sourcepattern -type string -optional 0
}
lassign [dict values [punk::args::get_dict $argspecs $args]] opts values
lassign [dict values [punk::args::get_dict $argspecs $args]] leaders opts values
set sourcepattern [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]

2
src/vfs/_vfscommon.vfs/modules/punk/path-0.1.0.tm

@ -662,7 +662,7 @@ namespace eval punk::path {
*values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1
} $args]
lassign [dict values $argd] opts values
lassign [dict values $argd] leaders opts values
set tailglobs [dict values $values]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]

17
src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.tm

@ -2857,7 +2857,7 @@ namespace eval repl {
set ::argc 0
set ::argv {}
set ::auto_path %autopath%
#puts stdout "safe interp"
#puts stdout "safebase interp"
#flush stdout
namespace eval ::codeinterp {
variable errstack {}
@ -2879,6 +2879,17 @@ namespace eval repl {
} else {
interp share {} [shellfilter::stack::item_tophandle stderr] code
}
interp eval code {
package require punk::lib
package require textblock ;#may fail to load term::ansi::code::macros - (only required for altg)
}
#JMN
interp eval code {
package require shellfilter
}
#work around bug in safe base which won't load Tcl libs that have deeper nesting
#(also affects tcllib page/plugins folder)
set termversions [package versions term]
@ -2896,13 +2907,15 @@ namespace eval repl {
}
#code invokehidden source c:/repo/jn/shellspy/modules/punk/lib-0.1.1.tm
code alias detok ::safe::DetokPath code
code alias detok ::safe::DetokPath code ;#temp - this violates the point of the {$p(:X:)} paths
#review - exit should do something slightly different
# see ::safe::interpDelete
code alias exit ::repl::interphelpers::quit
code alias ::md5::md5 ::repl::interphelpers::md5
code alias ::fconfigure ::fconfigure ;#needed for shellfilter
code alias ::file ::file
interp eval code [list package provide md5 $md5version]
} else {
interp create code

1491
src/vfs/_vfscommon.vfs/modules/punk/safe-0.1.0.tm

File diff suppressed because it is too large Load Diff

36
src/vfs/_vfscommon.vfs/modules/shellfilter-0.1.9.tm

@ -13,12 +13,24 @@
tcl::namespace::eval shellfilter::log {
variable allow_adhoc_tags 1
variable open_logs [tcl::dict::create]
variable is_enabled 0
proc disable {} {
variable is_enabled
set is_enabled 0
proc ::shellfilter::log::open {tag settingsdict} {}
proc ::shellfilter::log::write {tag msg} {}
proc ::shellfilter::log::write_sync {tag msg} {}
proc ::shellfilter::log::close {tag} {}
}
proc enable {} {
variable is_enabled
set is_enabled 1
#'tag' is an identifier for the log source.
# each tag will use it's own thread to write to the configured log target
proc open {tag {settingsdict {}}} {
proc ::shellfilter::log::open {tag {settingsdict {}}} {
upvar ::shellfilter::sources sourcelist
package require shellthread
if {![dict exists $settingsdict -tag]} {
tcl::dict::set settingsdict -tag $tag
} else {
@ -36,7 +48,7 @@ tcl::namespace::eval shellfilter::log {
#puts stderr "shellfilter::log::open this_threadid: [thread::id] tag: $tag worker_tid: $worker_tid"
return $worker_tid
}
proc write {tag msg} {
proc ::shellfilter::log::write {tag msg} {
upvar ::shellfilter::sources sourcelist
variable allow_adhoc_tags
if {!$allow_adhoc_tags} {
@ -47,14 +59,16 @@ tcl::namespace::eval shellfilter::log {
shellthread::manager::write_log $tag $msg
}
#write_sync - synchronous processing with logging thread, slower but potentially useful for debugging/testing or forcing delay til log written
proc write_sync {tag msg} {
proc ::shellfilter::log::write_sync {tag msg} {
shellthread::manager::write_log $tag $msg -async 0
}
proc close {tag} {
proc ::shellfilter::log::close {tag} {
#shellthread::manager::close_worker $tag
shellthread::manager::unsubscribe [list $tag]; #workertid will be added back to free list if no tags remain subscribed
}
}
#review
#configure whether we can call shellfilter::log::write without having called open first
proc require_open {{is_open_required {}}} {
@ -73,6 +87,12 @@ tcl::namespace::eval shellfilter::log {
}
}
}
if {[catch {package require shellthread}]} {
shellfilter::log::disable
} else {
shellfilter::log::enable
}
}
namespace eval shellfilter::pipe {
#write channel for program. workerthread reads other end of fifo2 and writes data somewhere
@ -1594,7 +1614,13 @@ namespace eval shellfilter::stack {
set tag "SHELLFILTER::STACK"
#JMN - load from config
#::shellfilter::log::open $tag {-syslog 127.0.0.1:514}
if {[catch {
::shellfilter::log::open $tag {-syslog ""}
} err]} {
#e.g safebase interp can't load required modules such as shellthread (or Thread)
puts stderr "shellfilter::show_pipeline cannot open log"
return
}
::shellfilter::log::write $tag "transform stack for $pipename $args"
foreach tf $stack {
::shellfilter::log::write $tag " $tf"

181
src/vfs/_vfscommon.vfs/modules/termscheme-0.1.0.tm

@ -0,0 +1,181 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# 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) 2024
#
# @@ Meta Begin
# Application termscheme 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_termscheme 0 0.1.0]
#[copyright "2024"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require termscheme]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of termscheme
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by termscheme
#[list_begin itemized]
package require Tcl 8.6-
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval termscheme::class {
#*** !doctools
#[subsection {Namespace termscheme::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval termscheme {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#*** !doctools
#[subsection {Namespace termscheme}]
#[para] Core API functions for termscheme
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace termscheme ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval termscheme::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace termscheme::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace termscheme::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval termscheme::system {
#*** !doctools
#[subsection {Namespace termscheme::system}]
#[para] Internal functions that are not part of the API
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide termscheme [tcl::namespace::eval termscheme {
variable pkg termscheme
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

15
src/vfs/_vfscommon.vfs/modules/textblock-0.1.2.tm

@ -62,7 +62,14 @@ catch {package require patternpunk}
package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
if {[catch {
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
} errM]} {
#catch this too in case stderr not available
catch {
puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
}
}
package require textutil
@ -5182,7 +5189,7 @@ tcl::namespace::eval textblock {
lassign [punk::args::get_dict {
-ansiresets -default 1 -type integer
blocks -type string -multiple 1
} $args] _o opts _v values
} $args] _l leaders _o opts _v values
set blocks [tcl::dict::get $values blocks]
set idx 0
@ -5844,7 +5851,7 @@ tcl::namespace::eval textblock {
*values -min 1
frametype -choices "<ftlist>" -choiceprefix 0 -choicerestricted 0 -type dict\
-help "name from the predefined frametypes or an adhoc dictionary."
memberglob -type globstring -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
memberglob -type globstring -optional 1 -multiple 1 -choiceprefix 0 -choicerestricted 0 -choices {
corner noncorner top bottom vertical horizontal left right
hl hlt hlb vsl vll vlr tlc trc blc brc hltj hlbj vllj vlrj
}\
@ -8110,7 +8117,7 @@ tcl::namespace::eval textblock {
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
*values -min 0 -max 1
size -default 1 -type integer
}
proc gcross {args} {

Loading…
Cancel
Save