# 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 }]