Browse Source

fix package loading, fix pipeline execution context to run in correct namespace, nscommands and ns/ multi-arg changes

master
Julian Noble 1 year ago
parent
commit
204d27b1b2
  1. 212
      src/modules/punk-0.1.tm
  2. 13
      src/modules/shellfilter-0.1.8.tm
  3. 79
      src/modules/shellrun-0.1.tm
  4. 70
      src/punk86.vfs/lib/app-punk/repl.tcl
  5. 4
      src/punk86.vfs/lib/app-shellspy/shellspy.tcl

212
src/modules/punk-0.1.tm

@ -4,7 +4,25 @@ package provide punk [namespace eval punk {
set version 0.1
}]
#Punk - where radical modification is a craft and anti-patterns are another exploratory tool for the Pattern Punk.
#Built on Tcl of course - because it's the most powerful piece of under-appreciated and alternate-thinking engineering you can plug into.
#repltelemetry cooperation with other packages such as shellrun
#Maintenance warning: shellrun expects repltelemetry_emmitters to exist if punk namespace exists
namespace eval punk {
variable repltelemetry_emmitters
#don't stomp.. even if something created this namespace in advance and is 'cooperating' a bit early
if {![info exists repltelemetry_emitters]} {
set repltelemetry_emmitters [list]
}
}
#globals... some minimal global var pollution
#punk's official silly test dictionary
set punk_testd [dict create \
a0 a0val \
b0 [dict create \
@ -12,30 +30,37 @@ set punk_testd [dict create \
b1 b0b1val \
c1 b0c1val \
d1 b0d1val \
]\
c0 [dict create \
a1 [dict create \
a2 c0a1a2val \
b2 c0a1b2val \
c2 c0a1c2val \
] \
c0 [dict create] \
d0 [dict create \
a1 [dict create \
a2 d0a1a2val \
b2 d0a1b2val \
c2 d0a1c2val \
] \
b1 [dict create \
a2 [dict create \
a3 c0b1a2a3val \
b3 c0b1a2b3val \
] \
b2 [dict create \
a3 c0b1b2a3val \
b3 [dict create \
a4 c0b1b2b3a4 \
] \
c3 [dict create] \
] \
] \
] \
b1 [dict create \
a2 [dict create \
a3 d0b1a2a3val \
b3 d0b1a2b3val \
] \
b2 [dict create \
a3 d0b1b2a3val \
bananas "in pyjamas" \
c3 [dict create \
po "in { }" \
b4 ""\
c4 "can go boom" \
] \
d3 [dict create \
a4 "-paper -cuts" \
] \
e3 [dict create] \
] \
] \
] \
]
#cooperative withe punk repl
#impolitely cooperative withe punk repl - todo - tone it down.
namespace eval ::repl {
variable running 0
}
@ -44,6 +69,7 @@ package require punk::config
namespace eval punk {
interp alias {} purelist {} lreplace x 0 0 ;#required by pipe system
package require pattern
package require shellfilter
package require punkapp
package require funcl
package require control
@ -79,10 +105,11 @@ namespace eval punk {
debug header "dbg> "
variable last_run_display [list]
variable ansi_disabled 0
variable colour_disabled 0
variable ns_current "::"
#variable re_headvar1 {([a-zA-Z:@.(),]+?)(?![^(]*\))(,.*)*$}
proc ::punk::K {x y} { return $x}
proc ::punk::var {varname {= {}} args} {
@ -2504,7 +2531,11 @@ namespace eval punk {
set ptype [string index $positionspecatomic 0]
set index [string range $positionspecatomic 1 end]
set isint [string is integer -strict $index]
if {$isint || [regexp {^(end|end[-+]{1,2}[0-9]+)$} $index]} {
if {$index eq "."} {
#blocking insertion-spec - explicit instruction not to pass this var in.
#most useful as just /. or data/. somevar/. is equivalent to leaving out the somevar insertionspec
#do nothing - no script
} elseif {$isint || [regexp {^(end|end[-+]{1,2}[0-9]+)$} $index]} {
if {$ptype eq "@"} {
#compare position to *possibly updated* list - note use of $index > $datalen rather than $index+1 > $datalen - (we allow 'insertion' at end of list by numeric index)
if {$isint} {
@ -2682,17 +2713,18 @@ namespace eval punk {
}
debug.punk.pipe.rep {>> [rep_listname segmentargvals]} 4
set ns [uplevel 1 {namespace current}]
if {!$add_argsdata} {
debug.punk.pipe {APPLY1: (args not set; not a list) segment vars:$segmentargnames} 4
#puts stderr " script: $script"
#puts stderr " vals: $segmentargvals"
set evaluation [uplevel 1 [list apply [list $segmentargnames $script ::] {*}$segmentargvals]]
set evaluation [uplevel 1 [list apply [list $segmentargnames $script $ns] {*}$segmentargvals]]
} else {
debug.punk.pipe {APPLY2: (args is set)segment vars:$segmentargnames} 4
#puts stderr " script: $script"
#puts stderr " vals: $segmentargvals $argsdatalist"
#pipeline script context should be one below calling context - so upvar v v will work
set evaluation [uplevel 1 [list apply [list [concat $segmentargnames args] $script ::] {*}$segmentargvals {*}$argsdatalist]]
set evaluation [uplevel 1 [list apply [list [concat $segmentargnames args] $script $ns] {*}$segmentargvals {*}$argsdatalist]]
}
debug.punk.pipe.rep {script result, evaluation: [rep_listname evaluation]} 4
@ -3473,29 +3505,31 @@ namespace eval punk {
#https://metacpan.org/pod/Text::ANSI::Util
#(saves up all ansi color codes since previus color reset and replays the saved codes after our highlighting is done)
proc ansi+ {args} {
variable ansi_disabled
if {$ansi_disabled == 1} {
variable colour_disabled
if {$colour_disabled == 1} {
return
}
tailcall ::shellfilter::ansi::+ {*}$args
}
proc ansi {{onoff {}}} {
variable ansi_disabled
proc colour {{onoff {}}} {
variable colour_disabled
if {[string length $onoff]} {
set onoff [string tolower $onoff]
if {$onoff in [list 1 on true yes]} {
interp alias "" a+ "" punk::ansi+
set ansi_disabled 0
set colour_disabled 0
} elseif {$onoff in [list 0 off false no]} {
interp alias "" a+ "" control::no-op
set ansi_disabled 1
set colour_disabled 1
} else {
error "punk::ansi expected 0|1|on|off|true|false|yes|no"
error "punk::colour expected 0|1|on|off|true|false|yes|no"
}
}
catch {repl::reset_prompt}
return [expr {!$ansi_disabled}]
return [expr {!$colour_disabled}]
}
proc scriptlibpath {{shortname {}} args} {
upvar ::punk::config::running running_config
set scriptlib [dict get $running_config scriptlib]
@ -3955,7 +3989,7 @@ namespace eval punk {
set fqpath $ns_current
}
}
puts stderr ">>fqpath $fqpath"
#puts stderr ">>fqpath $fqpath"
set nstail [namespace tail $glob]
if {[string first ? $nstail] >= 0 || [string first * $nstail] >=0} {
set location $fqpath
@ -4176,6 +4210,11 @@ namespace eval punk {
}
set is_absolute [string match ::* $a1]
if {$is_absolute} {
if {![llength $atail] && ([string first * [namespace tail $a1]] >= 0 || [string first ? [namespace tail $a1]] >= 0)} {
set out [punk::nslist $a1]
append out "\n$a1"
return $out
}
if {[namespace exists $a1]} {
set ns_current $a1
tailcall punk::ns/ {*}$atail
@ -4187,6 +4226,13 @@ namespace eval punk {
} else {
set nsnext ${ns_current}::$a1
}
if {![llength $atail] && ([string first * [namespace tail $a1]] >= 0)} {
set out [punk::nslist $nsnext]
append out "\n$nsnext"
return $out
}
if {[namespace exists $nsnext]} {
set ns_current $nsnext
tailcall punk::ns/ {*}$atail
@ -4801,7 +4847,8 @@ namespace eval punk {
interp alias {} hide {} punkapp::hide_console ;#will only work if controllable toplevels exist
interp alias {} ansi {} punk::ansi
interp alias {} colour {} punk::colour
interp alias {} color {} punk::colour
interp alias {} a+ {} punk::ansi+
#sh style 'test' and 'exitcode' (0 is false)
@ -4947,28 +4994,108 @@ namespace eval punk {
interp alias {} listset {} punk::listset ;#identical to pipeset
#experimental
#is there ever any difference to {namespace curent}?
interp alias {} nsthis {} .= .= namespace code {namespace current} |> .=* <0/#|
interp alias {} nsthis2 {} .= namespace current <0/#|
interp alias {} nscommands {} ,'ok'@0.= {
interp alias {} nscommands1 {} .= ,'ok'@0.= {
upvar caseresult caseresult
inspect -label namespace_current [namespace current]
inspect -label nsthis [nsthis]
inspect -label nsthis2 [nsthis2]
inspect -label commandns $commandns
inspect -label info_procs [info procs]
#by using pipeswitch instead of pipeswitchc - we give the ability* for the switch script block to affect vars in the calling scope
# (*not a desirable feature from a functional point of view - but useful for debugging, and also potentially faster )
pipeswitch {
#no glob chars present
if {![llength $ns]} {
set ns $commandns
} else {
if {![string match ::* $ns]} {
if {$commandns eq "::"} {set commandns ""}
set ns ${commandns}::$ns
}
}
inspect '$ns'
pipecase \
caseresult= $ns |input> \
1.= {expr {[string length [string map [list * "" ? ""] $data]] == [string length $data]}} |> {
uplevel #0 [list info commands ${input}::*]
#uplevel 1 [list info commands ${input}::*]
info commands ${input}::*
}
#pipecase1 ns has one or more of glob chars * or ?
pipecase \
caseresult= $ns |input> {
uplevel #0 [list info commands ${input}]
#uplevel 1 [list info commands ${input}]
info commands ${input}
}
}
} |data@@ok/result> {lmap v $data {namespace tail $v}} |> lsort |> {join $data \n} <ns/0|
interp alias {} nscommands {} .= ,'ok'@0.= {
upvar caseresult caseresult
#inspect -label namespace_current [namespace current]
#inspect -label info_procs [info procs]
set commandns [namespace current]
set commandlist [list]
#color code multiple args? - not very practical if many ns args supplied, but then... common case will only be 1 or 2 anyway
#colors can be stripped by caller with ansistrip - but we'll make the default case easier by using no ansi codes if a single arg is passed
set colors [list none cyan yellow green]
set ci 0 ;#colourindex
if {![llength $nslist]} {
lappend nslist *
}
foreach ns $nslist {
if {$ci > [llength $colors]-1} {
set ci 0
}
#by using pipeswitch instead of pipeswitchc - we give the ability* for the switch script block to affect vars in the calling scope
# (*not a desirable feature from a functional point of view - but useful for debugging, and also potentially faster )
if {$ci == 0} {
set col ""
} else {
set col [a+ [lindex $colors $ci] bold]
}
set matchedcommands [pipeswitch {
#no glob chars present
if {![llength $ns]} {
set ns $commandns
} else {
if {![string match ::* $ns]} {
if {$commandns eq "::"} {set commandns ""}
set ns ${commandns}::$ns
}
}
#inspect '$ns'
pipecase \
caseresult= $ns |input> \
1.= {expr {[string length [string map [list * "" ? ""] $data]] == [string length $data]}} |> {
#uplevel 1 [list info commands ${input}::*]
info commands ${input}::*
}
} |data@@ok/result> {lmap v $data {namespace tail $v}} |> lsort |> {join $data \n} <ns|
#pipecase1 ns has one or more of glob chars * or ?
pipecase \
caseresult= $ns |input> {
#uplevel 1 [list info commands ${input}]
info commands ${input}
}
}]
#lappend commandlist {*}[@@ok/result= $matchedcommands]
set rawcmds [@@ok/result= $matchedcommands |> {lmap v $data {namespace tail $v}}]
foreach c $rawcmds {
lappend commandlist [list $c $col$c[a+]]
#lappend commandlist $c
}
incr ci ;#colourindex
}
list ok [list result $commandlist]
} |data@@ok/result> lsort -index 0 |> {lmap v $data {lindex $v 1}} |> {join $data \n} <nslist|
#interp alias {} = {} ::punk::pipeline = "" ""
interp alias {} = {} ::punk::match_assign "" ""
@ -4978,13 +5105,6 @@ namespace eval punk {
# tailcall ::punk::pipeline .= "" "" {*}$args
#}
# new pipecommand syntax
proc =| {args} {
}
proc =/0| {args} {
}

13
src/modules/shellfilter-0.1.8.tm

@ -818,10 +818,21 @@ namespace eval shellfilter::chan {
## note - whether stack is for input or output we maintain it in the same direction - which is in sync with the tcl chan pop chan push concept.
##
namespace eval shellfilter::stack {
#todo - implement as oo
variable pipelines [list]
proc items {} {
#review - stdin,stdout,stderr act as pre-existing pipelines, and we can't create a new one with these names - so they should probably be autoconfigured and listed..
# - but in what contexts? only when we find them in [chan names]?
variable pipelines
return [dict keys $pipelines]
}
proc status {{pipename *} args} {
variable pipelines
set pipecount [dict size $pipelines]
set tableprefix "$pipecount pipelines active\n"
package require overtype
#todo -verbose
set table ""
@ -876,7 +887,7 @@ namespace eval shellfilter::stack {
}
}
return $table
return $tableprefix$table
}
#used for output channels - we usually want to sink redirections below the floaters and down to topmost existing redir
proc _get_stack_floaters {stack} {

79
src/modules/shellrun-0.1.tm

@ -7,7 +7,7 @@ package provide shellrun [namespace eval shellrun {
#purpose: handle the run commands that call shellfilter::run
#e.g run,runout,runerr,runx
package require shellfilter
#NOTE: the run,runout,runerr,runx commands only produce an error if the command didn't run.
# - If it did run, but there was a non-zero exitcode it is up to the application to check that.
@ -18,6 +18,63 @@ namespace eval shellrun {
variable runout
variable runerr
#do we need these?
variable punkout
variable punkerr
#some ugly coupling with punk/punk::config for now
#todo - something better
if {[info exists ::punk::config::running]} {
upvar ::punk::config::running conf
set syslog_stdout [dict get $conf syslog_stdout]
set syslog_stderr [dict get $conf syslog_stderr]
set logfile_stdout [dict get $conf logfile_stdout]
set logfile_stderr [dict get $conf logfile_stderr]
} else {
lassign [list "" "" "" ""] syslog_stdout syslog_stderr logfile_stdout logfile_stderr
}
set outdevice [shellfilter::stack::new punkout -settings [list -tag "punkout" -buffering none -raw 1 -syslog $syslog_stdout -file $logfile_stdout]]
set out [dict get $outdevice localchan]
set errdevice [shellfilter::stack::new punkerr -settings [list -tag "punkerr" -buffering none -raw 1 -syslog $syslog_stderr -file $logfile_stderr]]
set err [dict get $errdevice localchan]
#repltelemetry - additional/alternative display info used in a repl context i.e info directed towards the screen
#todo - package up in repltelemetry module and rewrite proc based on whether the module was found/loaded.
#somewhat strong coupling to punk - but let's try to behave decently if it's not loaded
#The last_run_display is actually intended for the repl - but is resident in the punk namespace with a view to the possibility of a different repl being in use.
proc set_last_run_display {chunklist} {
#chunklist as understood by the
if {![info exists ::punk::repltelemetry_emmitters]} {
namespace eval ::punk {
variable repltelemetry_emmitters
set repltelemetry_emmitters "shellrun"
}
} else {
if {"shellrun" ni $::punk::repltelemetry_emmitters} {
lappend punk::repltelemetry_emmitters "shellrun"
}
}
#most basic of validity tests here.. just that it is a list (can be empty). We don't want to duplicate or over-constrain the way repls/shells/terminals interpet the info
if {[catch {llength $chunklist} errMsg]} {
error "set_last_run_display expects a list. Value supplied doesn't appear to be a well formed tcl list. '$errMsg'"
}
#todo -
set ::punk::last_run_display $chunklist
}
if {![llength [info commands a+]]} {
#create the proc in this namespace only
proc a+ {args} {
shellfilter::ansi::+
}
}
proc get_run_opts {arglist} {
if {[catch {
set callerinfo [info level -1]
@ -46,7 +103,7 @@ namespace eval shellrun {
}
proc run {args} {
set ::punk::last_run_display [list]
set_last_run_display [list]
set splitargs [get_run_opts $args]
set runopts [dict get $splitargs runopts]
@ -82,8 +139,6 @@ namespace eval shellrun {
flush stderr
flush stdout
set c [a+ green]
set n [a+]
if {[dict exists $exitinfo error]} {
error "[dict get $exitinfo error]\n$exitinfo"
}
@ -92,7 +147,7 @@ namespace eval shellrun {
}
proc runout {args} {
set ::punk::last_run_display [list]
set_last_run_display [list]
variable runout
variable runerr
set runout ""
@ -198,7 +253,7 @@ namespace eval shellrun {
lappend chunklist [list result $chunk]
set ::punk::last_run_display $chunklist
set_last_run_display $chunklist
if {$nonewline} {
return [string trimright $::shellrun::runout \r\n]
@ -208,7 +263,7 @@ namespace eval shellrun {
}
proc runerr {args} {
set ::punk::last_run_display [list]
set_last_run_display [list]
variable runout
variable runerr
set runout ""
@ -299,7 +354,7 @@ namespace eval shellrun {
lappend chunklist [list resulterr $chunk]
set ::punk::last_run_display $chunklist
set_last_run_display $chunklist
if {$nonewline} {
return [string trimright $::shellrun::runerr \r\n]
@ -309,8 +364,7 @@ namespace eval shellrun {
proc runx {args} {
set ::punk::last_run_display [list]
variable last_run_display
set_last_run_display [list]
variable runout
variable runerr
set runout ""
@ -407,7 +461,7 @@ namespace eval shellrun {
}
lappend chunklist [list result "$c$exitinfo$n"]
set ::punk::last_run_display $chunklist
set_last_run_display $chunklist
#set ::repl::result_print 0
#return [lindex [list [list stdout $::runout stderr $::runerr {*}$exitinfo] [shellfilter::stack::remove stdout $x][puts -nonewline stdout $pretty][set ::repl::output ""]] 0]
@ -430,8 +484,7 @@ namespace eval shellrun {
}
#we can only call runraw with a single (presumably braced) string if we want to use it from both repl and tcl scripts
proc runraw {commandline} {
set ::punk::last_run_display [list]
variable last_run_display
set_last_run_display [list]
variable runout
variable runerr
set runout ""

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

@ -19,15 +19,21 @@ if {[dict exists $stdin_info -inputmode]} {
if {[dict exists $stdin_info -mode]} {
set tcl_interactive 1
}
#give up for now
set tcl_interactive 1
proc todo {} {
puts "tcl History"
puts "repltelemetry package"
puts "deaddrop package for a consistent way for modules to leave small notes to others that may come later."
}
set original_tm_list [tcl::tm::list]
tcl::tm::remove {*}$original_tm_list
if {[string match "*.vfs/*" [info script]]} {
#src/xxx.vfs/lib/app-punk/repl.tcl
#back 5 gives same level as src folder
@ -43,12 +49,32 @@ if {[file exists $modulefolder]} {
} else {
puts stderr "Warning unable to find module folder at: $modulefolder"
}
if {[file exists [pwd]/modules]} {
tcl::tm::add [pwd]/modules
}
package require Thread
#These are strong dependencies
# - the repl requires Threading and punk,shellfilter,shellrun to call and display properly.
# tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list
package forget shellfilter
package require shellfilter
package forget shellrun
package require shellrun
package forget punk
package require punk
#restore module paths
set tm_list_now [tcl::tm::list]
foreach p $original_tm_list {
if {$p ni $tm_list_now} {
tcl::tm::add $p
}
}
if {![info exists ::env(SHELL)]} {
set ::env(SHELL) punk86
}
@ -65,22 +91,6 @@ if {![info exists ::env(TERM)]} {
#}
}
package require Thread
#These are strong dependencies
# - the repl requires Threading and punk,shellfilter,shellrun to call and display properly.
# tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list
package forget shellfilter
package require shellfilter
package forget shellrun
package require shellrun
package forget punk
package require punk
#todo - move to less generic namespace
namespace eval repl {
variable screen_last_chars "" ;#a small sliding append buffer for last char of any screen output to detect \n vs string
@ -90,6 +100,7 @@ namespace eval repl {
variable prompt_reset_flag 0 ;#trigger repl to re-retrieve prompt settings
variable output ""
#important not to initialize - as it can be preset by cooperating package before app-punk has been package required
#(this is an example of a deaddrop)
variable post_script
variable signal_control_c 0
}
@ -168,10 +179,14 @@ interp alias {} smcup {} ::repl::term::screen_push_alt
interp alias {} rmcup {} ::repl::term::screen_pop_alt
set outdevice [shellfilter::stack::new punkout -settings [list -tag "punkout" -buffering none -raw 1 -syslog [dict get $::punk::config::running syslog_stdout] -file [dict get $::punk::config::running logfile_stdout]]]
set out [dict get $outdevice localchan]
set errdevice [shellfilter::stack::new punkerr -settings [list -tag "punkerr" -buffering none -raw 1 -syslog [dict get $::punk::config::running syslog_stderr] -file [dict get $::punk::config::running logfile_stderr]]]
set err [dict get $errdevice localchan]
# moved to punk package..
#set outdevice [shellfilter::stack::new punkout -settings [list -tag "punkout" -buffering none -raw 1 -syslog [dict get $::punk::config::running syslog_stdout] -file [dict get $::punk::config::running logfile_stdout]]]
#set out [dict get $outdevice localchan]
#set errdevice [shellfilter::stack::new punkerr -settings [list -tag "punkerr" -buffering none -raw 1 -syslog [dict get $::punk::config::running syslog_stderr] -file [dict get $::punk::config::running logfile_stderr]]]
#set err [dict get $errdevice localchan]
#
#set indevice [shellfilter::stack::new commandin -settings {-tag "commandin" -readbuffering line -writebuffering none -raw 1 -direction in}]
#set program_read_stdin_pipe [dict get $indevice localchan]
@ -957,11 +972,11 @@ proc repl::repl_handler {inputchan prompt_config} {
#***********************************************************
#don't use puts,rputs or debug_repl_emit in this block
#***********************************************************
if {[string length [dict get $running_config color_stdout]] && [punk::ansi]} {
if {[string length [dict get $running_config color_stdout]] && [punk::colour]} {
lappend outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]]
}
lappend outstack [shellfilter::stack::add stdout tee_to_var -settings {-varname ::repl::output_stdout}]
if {[string length [dict get $running_config color_stderr]] && [punk::ansi]} {
if {[string length [dict get $running_config color_stderr]] && [punk::colour]} {
lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]]
}
lappend errstack [shellfilter::stack::add stderr tee_to_var -settings {-varname ::repl::output_stderr}]
@ -980,7 +995,8 @@ proc repl::repl_handler {inputchan prompt_config} {
#puts stderr "repl uplevel 0 '$command'"
set status [catch {
#uplevel 1 $run_command_string
uplevel 1 {namespace eval $punk::ns_current $run_command_string}
#uplevel 1 {namespace eval $punk::ns_current $run_command_string}
uplevel 1 {namespace inscope $punk::ns_current $run_command_string}
} result]
}
#===============================================================================

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

@ -79,11 +79,11 @@ package require Thread
#NOTE: tm package index will probably already have been created so we must use 'package forget' to restrict to current tcl::tm::list path
#Review - effect on load time of wasting a previously created index? better way?
#require core modules only from punk distribution (REVIEW - override option?)
package foreget flagfilter
package forget flagfilter
package require flagfilter
package forget shellfilter
package require shellfilter
package foget punk
package forget punk
package require punk
#restore module paths

Loading…
Cancel
Save