You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
785 lines
31 KiB
785 lines
31 KiB
# vim: set ft=tcl |
|
# |
|
#purpose: handle the run commands that call shellfilter::run |
|
#e.g run,runout,runerr,runx |
|
|
|
package require shellfilter |
|
package require punk::ansi |
|
|
|
#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. |
|
#This is deliberate, but means 'catch' doesn't catch errors within the command itself - the exitcode has to be checked. |
|
#The user can always use exec for different process error semantics (they don't get exitcode with exec) |
|
|
|
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 |
|
} |
|
if {"punkshout" ni [shellfilter::stack::items]} { |
|
set outdevice [shellfilter::stack::new punkshout -settings [list -tag "punkshout" -buffering none -raw 1 -syslog $syslog_stdout -file $logfile_stdout]] |
|
set out [dict get $outdevice localchan] |
|
} else { |
|
set out [dict get [shellfilter::stack::item punkshout] device localchan] |
|
} |
|
if {"punksherr" ni [shellfilter::stack::items]} { |
|
set errdevice [shellfilter::stack::new punksherr -settings [list -tag "punksherr" -buffering none -raw 1 -syslog $syslog_stderr -file $logfile_stderr]] |
|
set err [dict get $errdevice localchan] |
|
} else { |
|
set err [dict get [shellfilter::stack::item punksherr] device localchan] |
|
} |
|
|
|
namespace import ::punk::ansi::a+ |
|
namespace import ::punk::ansi::a |
|
|
|
|
|
|
|
|
|
#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 - |
|
tsv::lappend repl runchunks-[tsv::get repl runid] {*}$chunklist |
|
} |
|
|
|
|
|
|
|
#maintenance: similar used in punk::ns & punk::winrun |
|
#todo - take runopts + aliases as args |
|
#longopts must be passed as a single item ie --timeout=100 not --timeout 100 |
|
proc get_run_opts {arglist} { |
|
if {[catch { |
|
set callerinfo [info level -1] |
|
} errM]} { |
|
set caller "" |
|
} else { |
|
set caller [lindex $callerinfo 0] |
|
} |
|
|
|
#we provide -nonewline even for 'run' even though run doesn't deliver stderr or stdout to the tcl return value |
|
#This is for compatibility with other runX commands, and the difference is also visible when calling from repl. |
|
set known_runopts [list "-echo" "-e" "-nonewline" "-n" "-tcl" "-debug"] |
|
set known_longopts [list "--timeout"] |
|
set known_longopts_msg "" |
|
foreach lng $known_longopts { |
|
append known_longopts_msg "${lng}=val " |
|
} |
|
set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline" "-tcl" "-tcl" "-debug" "-debug"] ;#include map to self |
|
set runopts [list] |
|
set runoptslong [list] |
|
set cmdargs [list] |
|
|
|
set idx_first_cmdarg [lsearch -not $arglist "-*"] |
|
|
|
set allopts [lrange $arglist 0 $idx_first_cmdarg-1] |
|
set cmdargs [lrange $arglist $idx_first_cmdarg end] |
|
foreach o $allopts { |
|
if {[string match --* $o]} { |
|
lassign [split $o =] flagpart valpart |
|
if {$valpart eq ""} { |
|
error "$caller: longopt $o seems to be missing a value - must be of form --option=value" |
|
} |
|
if {$flagpart ni $known_longopts} { |
|
error "$caller: Unknown runoption $o - known options $known_runopts $known_longopts_msg" |
|
} |
|
lappend runoptslong $flagpart $valpart |
|
} else { |
|
if {$o ni $known_runopts} { |
|
error "$caller: Unknown runoption $o - known options $known_runopts $known_longopts_msg" |
|
} |
|
lappend runopts [dict get $aliases $o] |
|
} |
|
} |
|
return [list runopts $runopts runoptslong $runoptslong cmdargs $cmdargs] |
|
} |
|
|
|
|
|
#todo - investigate cause of punk86 run hanging sometimes. An 'after 500' before exit in the called script fixes the issue. punk87 doesn't seem to be affected. |
|
proc run {args} { |
|
#set_last_run_display [list] |
|
|
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set runoptslong [dict get $splitargs runoptslong] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
|
|
if {"-nonewline" in $runopts} { |
|
set nonewline 1 |
|
} else { |
|
set nonewline 0 |
|
} |
|
set idlist_stderr [list] |
|
#we leave stdout without imposed ansi colouring - because the source may be colourised and because ansi-wrapping a stream at whatever boundaries it comes in at isn't a really nice thing to do. |
|
#stderr might have source colouring - but it usually doesn't seem to, and the visual distiction of red stderr can be very handy for the run command. |
|
#A further enhancement could be to detect well-known options such as --color and/or use a configuration for specific commands that have useful colourised stderr, |
|
#but having an option to configure stderr to red is a compromise. |
|
#Note that the other run commands, runout,runerr, runx don't emit in real-time - so for those commands there may be options to detect and/or post-process stdout and stderr. |
|
#TODO - fix. This has no effect if/when the repl adds an ansiwrap transform |
|
# what we probably want to do is 'aside' that transform for runxxx commands only. |
|
#lappend idlist_stderr [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}] |
|
|
|
set callopts [dict create] |
|
if {"-tcl" in $runopts} { |
|
dict set callopts -tclscript 1 |
|
} |
|
if {"-debug" in $runopts} { |
|
dict set callopts -debug 1 |
|
} |
|
if {[dict exists $runoptslong --timeout]} { |
|
dict set callopts -timeout [dict get $runoptslong --timeout] ;#convert to single dash |
|
} |
|
#--------------------------------------------------------------------------------------------- |
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none ] |
|
#--------------------------------------------------------------------------------------------- |
|
|
|
foreach id $idlist_stderr { |
|
shellfilter::stack::remove stderr $id |
|
} |
|
|
|
flush stderr |
|
flush stdout |
|
|
|
if {[dict exists $exitinfo error]} { |
|
error "[dict get $exitinfo error]\n$exitinfo" |
|
} |
|
|
|
return $exitinfo |
|
} |
|
|
|
#run in the way tcl unknown does - but without regard to auto_noexec |
|
proc runconsole {args} { |
|
if {![llength $args]} { |
|
error "no commandline specified" |
|
return |
|
} |
|
set name [lindex $args 0] |
|
set new [auto_execok $name] |
|
set repl_runid [punk::get_repl_runid] |
|
#set ::punk::last_run_display [list] |
|
|
|
set redir ">&@stdout <@stdin" |
|
uplevel 1 [list ::catch [concat exec $redir $new [lrange $args 1 end]] ::tcl::UnknownResult ::tcl::UnknownOptions] |
|
#we can't detect stdout/stderr output from the exec |
|
#for now emit an extra \n on stderr |
|
#todo - there is probably no way around this but to somehow exec in the context of a completely separate console |
|
#This is probably a tricky problem - especially to do cross-platform |
|
# |
|
# - use [dict get $::tcl::UnknownOptions -code] (0|1) exit |
|
if {[dict get $::tcl::UnknownOptions -code] == 0} { |
|
set c green |
|
set m "ok" |
|
} else { |
|
set c yellow |
|
set m "errorCode $::errorCode" |
|
} |
|
set chunklist [list] |
|
lappend chunklist [list "info" "[a $c]$m[a] " ] |
|
if {$repl_runid != 0} { |
|
tsv::lappend repl runchunks-$repl_runid {*}$chunklist |
|
} |
|
|
|
dict incr ::tcl::UnknownOptions -level |
|
return -options $::tcl::UnknownOptions $::tcl::UnknownResult |
|
} |
|
proc runout {args} { |
|
#set_last_run_display [list] |
|
variable runout |
|
variable runerr |
|
set runout "" |
|
set runerr "" |
|
set RST [a] |
|
|
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
|
|
if {"-nonewline" in $runopts} { |
|
set nonewline 1 |
|
} else { |
|
set nonewline 0 |
|
} |
|
|
|
#puts stdout "RUNOUT cmdargs: $cmdargs" |
|
|
|
#todo add -data boolean and -data lastwrite to -settings with default being -data all |
|
# because sometimes we're only interested in last char (e.g to detect something was output) |
|
|
|
#set outvar_stackid [shellfilter::stack::add commandout tee_to_var -action float -settings {-varname ::runout}] |
|
# |
|
#when not echoing - use float-locked so that the repl's stack is bypassed |
|
if {"-echo" in $runopts} { |
|
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float-locked -settings {-varname ::shellrun::runout}] |
|
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float-locked -settings {-varname ::shellrun::runerr}] |
|
#set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action sink-locked -settings {-varname ::shellrun::runerr}] |
|
} else { |
|
set stdout_stackid [shellfilter::stack::add stdout var -action float-locked -settings {-varname ::shellrun::runout}] |
|
set stderr_stackid [shellfilter::stack::add stderr var -action float-locked -settings {-varname ::shellrun::runerr}] |
|
} |
|
|
|
set callopts "" |
|
if {"-tcl" in $runopts} { |
|
append callopts " -tclscript 1" |
|
} |
|
|
|
#shellfilter::run [lrange $args 1 end] -teehandle punksh -outchan stdout -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler |
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none ] |
|
|
|
flush stderr |
|
flush stdout |
|
|
|
shellfilter::stack::remove stdout $stdout_stackid |
|
shellfilter::stack::remove stderr $stderr_stackid |
|
|
|
#shellfilter::stack::remove commandout $outvar_stackid |
|
|
|
if {[dict exists $exitinfo error]} { |
|
if {"-tcl" in $runopts} { |
|
|
|
} else { |
|
#we must raise an error. |
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
|
# |
|
set msg "" |
|
append msg [dict get $exitinfo error] |
|
append msg "\n(add -tcl option to run as a tcl command/script instead of an external command)" |
|
error $msg |
|
} |
|
} |
|
|
|
set chunklist [list] |
|
|
|
#exitcode not part of return value for runout - colourcode appropriately |
|
set n $RST |
|
set c "" |
|
if [dict exists $exitinfo exitcode] { |
|
set code [dict get $exitinfo exitcode] |
|
if {$code == 0} { |
|
set c [a+ green] |
|
} else { |
|
set c [a+ white bold] |
|
} |
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
|
} elseif [dict exists $exitinfo error] { |
|
set c [a+ yellow bold] |
|
lappend chunklist [list "info" "${c}error [dict get $exitinfo error]$n"] |
|
lappend chunklist [list "info" "errorCode [dict get $exitinfo errorCode]"] |
|
#lappend chunklist [list "info" "errorInfo [list [dict get $exitinfo errorInfo]]"] |
|
lappend chunklist [list "info" errorInfo] |
|
lappend chunklist [list "stderr" [dict get $exitinfo errorInfo]] |
|
} else { |
|
set c [a+ Yellow red bold] |
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
|
} |
|
|
|
|
|
set chunk "[a+ red bold]stderr$RST" |
|
lappend chunklist [list "info" $chunk] |
|
|
|
set chunk "" |
|
if {[string length $::shellrun::runerr]} { |
|
if {$nonewline} { |
|
set e [string trimright $::shellrun::runerr \r\n] |
|
} else { |
|
set e $::shellrun::runerr |
|
} |
|
#append chunk "[a+ red normal]$e$RST\n" |
|
append chunk "[a+ red normal]$e$RST" |
|
} |
|
lappend chunklist [list stderr $chunk] |
|
|
|
|
|
|
|
|
|
lappend chunklist [list "info" "[a+ white bold]stdout$RST"] |
|
set chunk "" |
|
if {[string length $::shellrun::runout]} { |
|
if {$nonewline} { |
|
set o [string trimright $::shellrun::runout \r\n] |
|
} else { |
|
set o $::shellrun::runout |
|
} |
|
append chunk "$o" |
|
} |
|
lappend chunklist [list result $chunk] |
|
|
|
|
|
#set_last_run_display $chunklist |
|
tsv::lappend repl runchunks-[tsv::get repl runid] {*}$chunklist |
|
|
|
if {$nonewline} { |
|
return [string trimright $::shellrun::runout \r\n] |
|
} else { |
|
return $::shellrun::runout |
|
} |
|
} |
|
|
|
proc runerr {args} { |
|
#set_last_run_display [list] |
|
variable runout |
|
variable runerr |
|
set runout "" |
|
set runerr "" |
|
|
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
|
|
if {"-nonewline" in $runopts} { |
|
set nonewline 1 |
|
} else { |
|
set nonewline 0 |
|
} |
|
|
|
set callopts "" |
|
if {"-tcl" in $runopts} { |
|
append callopts " -tclscript 1" |
|
} |
|
if {"-echo" in $runopts} { |
|
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float-locked -settings {-varname ::shellrun::runerr}] |
|
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float-locked -settings {-varname ::shellrun::runout}] |
|
} else { |
|
set stderr_stackid [shellfilter::stack::add stderr var -action float-locked -settings {-varname ::shellrun::runerr}] |
|
set stdout_stackid [shellfilter::stack::add stdout var -action float-locked -settings {-varname ::shellrun::runout}] |
|
} |
|
|
|
|
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
|
shellfilter::stack::remove stderr $stderr_stackid |
|
shellfilter::stack::remove stdout $stdout_stackid |
|
|
|
|
|
flush stderr |
|
flush stdout |
|
|
|
#we raise an error because an error during calling is different to collecting stderr from a command, and the caller should be able to wrap in a catch |
|
# to determine something other than just a nonzero exit code or output on stderr. |
|
if {[dict exists $exitinfo error]} { |
|
if {"-tcl" in $runopts} { |
|
|
|
} else { |
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
|
error [dict get $exitinfo error] |
|
} |
|
} |
|
|
|
set chunklist [list] |
|
|
|
set n [a] |
|
set c "" |
|
if [dict exists $exitinfo exitcode] { |
|
set code [dict get $exitinfo exitcode] |
|
if {$code == 0} { |
|
set c [a+ green] |
|
} else { |
|
set c [a+ white bold] |
|
} |
|
|
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
|
|
|
} elseif [dict exists $exitinfo error] { |
|
set c [a+ yellow bold] |
|
lappend chunklist [list "info" "error [dict get $exitinfo error]"] |
|
lappend chunklist [list "info" "errorCode [dict get $exitinfo errorCode]"] |
|
lappend chunklist [list "info" "errorInfo [list [dict get $exitinfo errorInfo]]"] |
|
} else { |
|
set c [a+ Yellow red bold] |
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
|
} |
|
|
|
|
|
lappend chunklist [list "info" "[a+ white bold]stdout[a]"] |
|
set chunk "" |
|
if {[string length $::shellrun::runout]} { |
|
if {$nonewline} { |
|
set o [string trimright $::shellrun::runout \r\n] |
|
} else { |
|
set o $::shellrun::runout |
|
} |
|
append chunk "[a+ white normal]$o[a]\n" ;#this newline is the display output separator - always there whether data has trailing newline or not. |
|
} |
|
lappend chunklist [list stdout $chunk] |
|
|
|
|
|
#set c_stderr [punk::config] |
|
set chunk "[a+ red bold]stderr[a]" |
|
lappend chunklist [list "info" $chunk] |
|
|
|
set chunk "" |
|
if {[string length $::shellrun::runerr]} { |
|
if {$nonewline} { |
|
set e [string trimright $::shellrun::runerr \r\n] |
|
} else { |
|
set e $::shellrun::runerr |
|
} |
|
append chunk "$e" |
|
} |
|
lappend chunklist [list resulterr $chunk] |
|
|
|
|
|
#set_last_run_display $chunklist |
|
tsv::lappend repl runchunks-[tsv::get repl runid] {*}$chunklist |
|
|
|
if {$nonewline} { |
|
return [string trimright $::shellrun::runerr \r\n] |
|
} |
|
return $::shellrun::runerr |
|
} |
|
|
|
|
|
proc runx {args} { |
|
#set_last_run_display [list] |
|
variable runout |
|
variable runerr |
|
set runout "" |
|
set runerr "" |
|
|
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
|
|
if {"-nonewline" in $runopts} { |
|
set nonewline 1 |
|
} else { |
|
set nonewline 0 |
|
} |
|
|
|
#shellfilter::stack::remove stdout $::repl::id_outstack |
|
|
|
if {"-echo" in $runopts} { |
|
#float to ensure repl transform doesn't interfere with the output data |
|
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float -settings {-varname ::shellrun::runerr}] |
|
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float -settings {-varname ::shellrun::runout}] |
|
} else { |
|
#set stderr_stackid [shellfilter::stack::add stderr var -action sink-locked -settings {-varname ::shellrun::runerr}] |
|
#set stdout_stackid [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::shellrun::runout}] |
|
|
|
#float above the repl's tee_to_var to deliberately block it. |
|
#a var transform is naturally a junction point because there is no flow-through.. |
|
# - but mark it with -junction 1 just to be explicit |
|
set stderr_stackid [shellfilter::stack::add stderr var -action float-locked -junction 1 -settings {-varname ::shellrun::runerr}] |
|
set stdout_stackid [shellfilter::stack::add stdout var -action float-locked -junction 1 -settings {-varname ::shellrun::runout}] |
|
} |
|
|
|
set callopts "" |
|
if {"-tcl" in $runopts} { |
|
append callopts " -tclscript 1" |
|
} |
|
#set exitinfo [shellfilter::run $cmdargs -teehandle punksh -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none] |
|
|
|
shellfilter::stack::remove stdout $stdout_stackid |
|
shellfilter::stack::remove stderr $stderr_stackid |
|
|
|
|
|
flush stderr |
|
flush stdout |
|
|
|
if {[dict exists $exitinfo error]} { |
|
if {"-tcl" in $runopts} { |
|
|
|
} else { |
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
|
error [dict get $exitinfo error] |
|
} |
|
} |
|
|
|
#set x [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::repl::runxoutput}] |
|
|
|
set chunk "" |
|
if {[string length $::shellrun::runout]} { |
|
if {$nonewline} { |
|
set o [string trimright $::shellrun::runout \r\n] |
|
} else { |
|
set o $::shellrun::runout |
|
} |
|
set chunk $o |
|
} |
|
set chunklist [list] |
|
lappend chunklist [list "info" " "] |
|
lappend chunklist [list "result" stdout] ;#key 'stdout' forms part of the resulting dictionary output |
|
lappend chunklist [list "info" "[a+ white bold]stdout[a]"] |
|
lappend chunklist [list result $chunk] ;#value corresponding to 'stdout' key in resulting dict |
|
|
|
|
|
lappend chunklist [list "info" " "] |
|
set chunk "[a+ red bold]stderr[a]" |
|
lappend chunklist [list "result" $chunk] |
|
lappend chunklist [list "info" stderr] |
|
|
|
set chunk "" |
|
if {[string length $::shellrun::runerr]} { |
|
if {$nonewline} { |
|
set e [string trimright $::shellrun::runerr \r\n] |
|
} else { |
|
set e $::shellrun::runerr |
|
} |
|
set chunk $e |
|
} |
|
#stderr is part of the result |
|
lappend chunklist [list "resulterr" $chunk] |
|
|
|
|
|
|
|
set n [a] |
|
set c "" |
|
if {[dict exists $exitinfo exitcode]} { |
|
set code [dict get $exitinfo exitcode] |
|
if {$code == 0} { |
|
set c [a+ green] |
|
} else { |
|
set c [a+ yellow bold] |
|
} |
|
lappend chunklist [list "info" " "] |
|
lappend chunklist [list "result" exitcode] |
|
lappend chunklist [list "info" "exitcode $code"] |
|
lappend chunklist [list "result" "$c$code$n"] |
|
set exitdict [list exitcode $code] |
|
} elseif {[dict exists $exitinfo result]} { |
|
# presumably from a -tcl call |
|
set val [dict get $exitinfo result] |
|
lappend chunklist [list "info" " "] |
|
lappend chunklist [list "result" result] |
|
lappend chunklist [list "info" result] |
|
lappend chunklist [list "result" $val] |
|
set exitdict [list result $val] |
|
} elseif {[dict exists $exitinfo error]} { |
|
# -tcl call with error |
|
#set exitdict [dict create] |
|
lappend chunklist [list "info" " "] |
|
lappend chunklist [list "result" error] |
|
lappend chunklist [list "info" error] |
|
lappend chunklist [list "result" [dict get $exitinfo error]] |
|
|
|
lappend chunklist [list "info" " "] |
|
lappend chunklist [list "result" errorCode] |
|
lappend chunklist [list "info" errorCode] |
|
lappend chunklist [list "result" [dict get $exitinfo errorCode]] |
|
|
|
lappend chunklist [list "info" " "] |
|
lappend chunklist [list "result" errorInfo] |
|
lappend chunklist [list "info" errorInfo] |
|
lappend chunklist [list "result" [dict get $exitinfo errorInfo]] |
|
|
|
set exitdict $exitinfo |
|
} else { |
|
#review - if no exitcode or result. then what is it? |
|
lappend chunklist [list "info" exitinfo] |
|
set c [a+ yellow bold] |
|
lappend chunklist [list result "$c$exitinfo$n"] |
|
set exitdict [list exitinfo $exitinfo] |
|
} |
|
|
|
#set_last_run_display $chunklist |
|
tsv::lappend repl runchunks-[tsv::get repl runid] {*}$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] |
|
|
|
|
|
if {$nonewline} { |
|
return [list {*}$exitdict stdout [string trimright $::shellrun::runout \r\n] stderr [string trimright $::shellrun::runerr \r\n]] |
|
} |
|
#always return exitinfo $code at beginning of dict (so that punk unknown can interpret the exit code as a unix-style bool if double evaluated) |
|
return [list {*}$exitdict stdout $::shellrun::runout stderr $::shellrun::runerr] |
|
} |
|
|
|
#an experiment |
|
# |
|
#run as raw string instead of tcl-list - no variable subst etc |
|
# |
|
#dummy repl_runraw that repl will intercept |
|
proc repl_runraw {args} { |
|
error "runraw: only available in repl as direct call - not from script" |
|
} |
|
#we can only call runraw with a single (presumably braced) string if we want to use it from both repl and tcl scripts (why? todo with unbalanced quotes/braces?) |
|
proc runraw {commandline} { |
|
#runraw fails as intended - because we can't bypass exec/open interference quoting :/ |
|
#set_last_run_display [list] |
|
variable runout |
|
variable runerr |
|
set runout "" |
|
set runerr "" |
|
|
|
#return [shellfilter::run [lrange $args 1 end] -teehandle punksh -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
|
puts stdout ">>runraw got: $commandline" |
|
|
|
#run always echoes anyway.. as we aren't diverting stdout/stderr off for capturing |
|
#for consistency with other runxxx commands - we'll just consume it. (review) |
|
|
|
set reallyraw 1 |
|
if {$reallyraw} { |
|
set wordparts [regexp -inline -all {\S+} $commandline] |
|
set runwords $wordparts |
|
} else { |
|
#shell style args parsing not suitable for windows where we can't assume matched quotes etc. |
|
package require string::token::shell |
|
set parts [string token shell -indices -- $commandline] |
|
puts stdout ">>shellparts: $parts" |
|
set runwords [list] |
|
foreach p $parts { |
|
set ptype [lindex $p 0] |
|
set pval [lindex $p 3] |
|
if {$ptype eq "PLAIN"} { |
|
lappend runwords [lindex $p 3] |
|
} elseif {$ptype eq "D:QUOTED"} { |
|
set v {"} |
|
append v $pval |
|
append v {"} |
|
lappend runwords $v |
|
} elseif {$ptype eq "S:QUOTED"} { |
|
set v {'} |
|
append v $pval |
|
append v {'} |
|
lappend runwords $v |
|
} |
|
} |
|
} |
|
|
|
puts stdout ">>runraw runwords: $runwords" |
|
set runwords [lrange $runwords 1 end] |
|
|
|
puts stdout ">>runraw runwords: $runwords" |
|
#set args [lrange $args 1 end] |
|
#set runwords [lrange $wordparts 1 end] |
|
|
|
set known_runopts [list "-echo" "-e" "-terminal" "-t"] |
|
set aliases [list "-e" "-echo" "-echo" "-echo" "-t" "-terminal" "-terminal" "-terminal"] ;#include map to self |
|
set runopts [list] |
|
set cmdwords [list] |
|
set idx_first_cmdarg [lsearch -not $runwords "-*"] |
|
set runopts [lrange $runwords 0 $idx_first_cmdarg-1] |
|
set cmdwords [lrange $runwords $idx_first_cmdarg end] |
|
|
|
foreach o $runopts { |
|
if {$o ni $known_runopts} { |
|
error "runraw: Unknown runoption $o" |
|
} |
|
} |
|
set runopts [lmap o $runopts {dict get $aliases $o}] |
|
|
|
set cmd_as_string [join $cmdwords " "] |
|
puts stdout ">>cmd_as_string: $cmd_as_string" |
|
|
|
if {"-terminal" in $runopts} { |
|
#fake terminal using 'script' command. |
|
#not ideal: smushes stdout & stderr together amongst other problems |
|
set tcmd [shellfilter::get_scriptrun_from_cmdlist_dquote_if_not $cmdwords] |
|
puts stdout ">>tcmd: $tcmd" |
|
set exitinfo [shellfilter::run $tcmd -teehandle punksh -inbuffering line -outbuffering none ] |
|
set exitinfo "exitcode not-implemented" |
|
} else { |
|
set exitinfo [shellfilter::run $cmdwords -teehandle punksh -inbuffering line -outbuffering none ] |
|
} |
|
|
|
if {[dict exists $exitinfo error]} { |
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
|
error [dict get $exitinfo error] |
|
} |
|
set code [dict get $exitinfo exitcode] |
|
if {$code == 0} { |
|
set c [a+ green] |
|
} else { |
|
set c [a+ white bold] |
|
} |
|
puts stderr $c |
|
return $exitinfo |
|
} |
|
|
|
proc sh_run {args} { |
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
#e.g sh -c "ls -l *" |
|
#we pass cmdargs to sh -c as a list, not individually |
|
tailcall shellrun::run {*}$runopts sh -c $cmdargs |
|
} |
|
proc sh_runout {args} { |
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
tailcall shellrun::runout {*}$runopts sh -c $cmdargs |
|
} |
|
proc sh_runerr {args} { |
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
tailcall shellrun::runerr {*}$runopts sh -c $cmdargs |
|
} |
|
proc sh_runx {args} { |
|
set splitargs [get_run_opts $args] |
|
set runopts [dict get $splitargs runopts] |
|
set cmdargs [dict get $splitargs cmdargs] |
|
tailcall shellrun::runx {*}$runopts sh -c $cmdargs |
|
} |
|
} |
|
|
|
namespace eval shellrun { |
|
interp alias {} run {} shellrun::run |
|
interp alias {} sh_run {} shellrun::sh_run |
|
interp alias {} runout {} shellrun::runout |
|
interp alias {} sh_runout {} shellrun::sh_runout |
|
interp alias {} runerr {} shellrun::runerr |
|
interp alias {} sh_runerr {} shellrun::sh_runerr |
|
interp alias {} runx {} shellrun::runx |
|
interp alias {} sh_runx {} shellrun::sh_runx |
|
|
|
interp alias {} runc {} shellrun::runconsole |
|
interp alias {} runraw {} shellrun::runraw |
|
|
|
|
|
#the shortened versions deliberately don't get pretty output from the repl |
|
interp alias {} r {} shellrun::run |
|
interp alias {} ro {} shellrun::runout |
|
interp alias {} re {} shellrun::runerr |
|
interp alias {} rx {} shellrun::runx |
|
|
|
|
|
} |
|
|
|
namespace eval shellrun { |
|
proc test_cffi {} { |
|
package require test_cffi |
|
cffi::Wrapper create ::shellrun::kernel32 [file join $env(windir) system32 Kernel32.dll] |
|
::shellrun::kernel32 stdcall CreateProcessA |
|
#todo - stuff. |
|
return ::shellrun::kernel32 |
|
} |
|
|
|
} |
|
|
|
package provide shellrun [namespace eval shellrun { |
|
variable version |
|
set version 0.1.1 |
|
}]
|
|
|