Julian Noble
2 years ago
6 changed files with 1322 additions and 428 deletions
@ -0,0 +1,265 @@
|
||||
#<?xml version="1.0"?> |
||||
#<xml> |
||||
#<xpack> |
||||
#<code> |
||||
#<![CDATA[ |
||||
# Author: Julian Marcel Noble <julian@cyberclad.com> |
||||
# 2004 - Public Domain |
||||
# |
||||
# PatternPunk - DIALECT |
||||
#Dynamic Instance Accumulation Language Extending Classic Tcl |
||||
#The goofy acronym is a fancy way of not referring to PatternPunk as yet another OO system. |
||||
|
||||
|
||||
package require pattern |
||||
package require overtype |
||||
pattern::init |
||||
|
||||
package provide patternpunk [namespace eval punk { |
||||
variable version |
||||
|
||||
set version 1.1 |
||||
}] |
||||
|
||||
|
||||
::>pattern .. Create ::>punk |
||||
::>punk .. Property license {Public Domain} |
||||
::>punk .. Property logo2 { |
||||
+-----------------------+ |
||||
| Pattern PUNK | |
||||
| . \\\_ . | |
||||
| .*. \@ > .=. | |
||||
| .*.*. | ~ .=.=. | |
||||
|.*.*.*.\_- -_/.=.=.=.| |
||||
| .*.*. \\ .=.=. | |
||||
| .*. / \ .=. | |
||||
| . _+ +_ . | |
||||
+-----------------------+ |
||||
} |
||||
set ::punk::bannerTemplate { |
||||
+-----------------------+ |
||||
| .000000000000000. | |
||||
| .*. \\\_ .=. | |
||||
| .*.*. \@ > .=.=. | |
||||
|.*.*.*. | ~ .=.=.=.| |
||||
| .*.*. \_- -_/ .=.=. | |
||||
| .*. \\ .=. | |
||||
| . / \ . | |
||||
|111111111_+ +_2222222| |
||||
+-----------------------+ |
||||
} |
||||
|
||||
>punk .. Method banner {args} { |
||||
set defaults [list -title "Pattern PUNK" -left "" -right ""] |
||||
if {[catch {set opts [dict merge $defaults $args]} ]} { |
||||
error "usage: banner \[-title \$title -left \$left -right \$right\]" |
||||
} |
||||
|
||||
set word1 [overtype::left [string repeat " " 9] [dict get $opts -left]] |
||||
set word2 [overtype::right [string repeat " " 7] [dict get $opts -right]] |
||||
set title [overtype::centre [string repeat " " 15] [dict get $opts -title]] |
||||
|
||||
return [string map [list 111111111 $word1 2222222 $word2 000000000000000 $title] $punk::bannerTemplate] |
||||
} |
||||
|
||||
|
||||
|
||||
>punk .. Property logo [>punk . banner] |
||||
>punk .. Property versionLogo [>punk . banner -left " Ver" -right "$::punk::version "] |
||||
>punk .. Property version $::punk::version |
||||
|
||||
>punk .. Property front { |
||||
_|_ |
||||
@ v @ |
||||
~ |
||||
- - |
||||
|_\ /_| |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
>punk .. Property back { |
||||
| |
||||
( | ) |
||||
| |
||||
- - |
||||
|_\ /_| |
||||
/ \ |
||||
_- -_ |
||||
} |
||||
>punk .. Property rhs { |
||||
\\\_ |
||||
\@ > |
||||
| ~ |
||||
\_- -_ |
||||
\\ / |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
>punk .. Property right |
||||
>punk .. PropertyRead right {} { |
||||
return $o_rhs |
||||
} |
||||
|
||||
|
||||
>punk .. Property lhs { |
||||
_/// |
||||
< @/ |
||||
~ | |
||||
_- -_/ |
||||
\ // |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
>punk .. Property left |
||||
>punk .. PropertyRead left {} { |
||||
return $o_lhs |
||||
} |
||||
|
||||
>punk .. Property rhs_air { |
||||
\\\_ |
||||
\@ > |
||||
| ~ |
||||
\_- -_/ |
||||
\\ |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
>punk .. Property lhs_air { |
||||
_/// |
||||
< @/ |
||||
~ | |
||||
\_- -_/ |
||||
// |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
|
||||
>punk .. Property lhs_hips { |
||||
_/// |
||||
< @/ |
||||
~ | |
||||
_- -_ |
||||
\ | | / |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
>punk .. Property rhs_hips { |
||||
\\\_ |
||||
\@ > |
||||
| ~ |
||||
_- -_ |
||||
\ | | / |
||||
/ \ |
||||
_+ +_ |
||||
} |
||||
|
||||
|
||||
>punk .. Property piss { |
||||
\\\_ |
||||
\@ > |
||||
| ~ |
||||
\_- -_/ |
||||
\\_ .. |
||||
/ \ .. |
||||
_+ +_ . |
||||
} |
||||
|
||||
>punk .. Property poop { |
||||
_/// |
||||
< @/ |
||||
^ | |
||||
_- -_ |
||||
\ \\ / |
||||
//. ~ |
||||
_+_+ @ |
||||
} |
||||
|
||||
>punk .. Method dumpProperties {{object ::>punk}} { |
||||
foreach {p v} [$object .. Properties . pairs] { |
||||
puts $p |
||||
puts [set $v] |
||||
puts \n |
||||
} |
||||
} |
||||
>punk .. Method listProperties {{object ::>punk}} { |
||||
set result {} |
||||
foreach {p v} [$object .. Properties . pairs] { |
||||
lappend result $p [set $v] |
||||
} |
||||
return $result |
||||
} |
||||
|
||||
|
||||
########################################################## |
||||
#CANDY-CODE |
||||
# |
||||
#!todo - unset etc. |
||||
if {[info proc ::punk::_unknown] eq ""} {rename unknown ::punk::_unknown} |
||||
|
||||
proc ::punk::var {varname {= {}} args} { |
||||
if {${=} == "="} { |
||||
if {[llength $args] > 1} { |
||||
uplevel 1 [list set $varname [uplevel 1 $args]] |
||||
} else { |
||||
uplevel 1 [list set $varname [lindex $args 0]] |
||||
} |
||||
} else { |
||||
uplevel 1 [list set $varname] |
||||
} |
||||
} |
||||
proc unknown {args} { |
||||
if {[lindex $args 1] eq "="} { |
||||
set n [lindex $args 0] |
||||
set v [lindex $args 2] |
||||
#uplevel 1 [string map [list @n@ $n @v@ $v] {proc @n@ {= val} {uplevel 1 set @n@ $val}}] |
||||
uplevel 1 [list interp alias {} $n {} ::punk::var $n] |
||||
|
||||
#uplevel 1 [list trace add variable $n unset [string map [list @n@ $n] {uplevel 1 [list interp alias {} @n@ {}]}]] |
||||
uplevel 1 [list trace add variable $n unset [list interp alias {} $n {}]] |
||||
|
||||
if {[llength $args] > 3} { |
||||
#RHS consists of multiple args; evaluate |
||||
return [uplevel 1 [list set $n [uplevel 1 [lrange $args 2 end]]]] |
||||
} else { |
||||
#RHS is single arg; treat as value |
||||
return [uplevel 1 [list set $n $v]] |
||||
} |
||||
} else { |
||||
#delegate to original 'unknown' command |
||||
uplevel 1 ::punk::_unknown $args |
||||
} |
||||
} |
||||
|
||||
|
||||
#Cute names for file I/O |
||||
proc <- filename { |
||||
set fp [open $filename] |
||||
::pattern::K [read $fp] [close $fp] |
||||
} |
||||
proc -> {filename string} { |
||||
set fp [open $filename w] |
||||
puts $fp $string |
||||
close $fp |
||||
} |
||||
proc ->> {filename string} { |
||||
set fp [open $filename a] |
||||
puts $fp $string |
||||
close $fp |
||||
} |
||||
|
||||
#presumably this is to allow calling of standard objects using dotted notation? |
||||
::>pattern .. Create ::> |
||||
::> .. Method item {args} { |
||||
#uplevel #0 $args |
||||
#uplevel #0 [join $args] |
||||
|
||||
uplevel #0 $args |
||||
} |
||||
|
||||
#]]> |
||||
#</code> |
||||
#<files> |
||||
#</files> |
||||
#</xpack> |
||||
#</xml> |
||||
|
@ -0,0 +1,539 @@
|
||||
package provide punk [namespace eval punk { |
||||
variable version |
||||
set version 0.1 |
||||
}] |
||||
|
||||
namespace eval punk::config { |
||||
variable loaded |
||||
variable startup ;#include env overrides |
||||
variable running |
||||
|
||||
set vars [list \ |
||||
scriptlib \ |
||||
color_stdout \ |
||||
color_stderr \ |
||||
logfile_stdout \ |
||||
logfile_stderr \ |
||||
syslog_stdout \ |
||||
syslog_stderr \ |
||||
exec_unknown \ |
||||
] |
||||
#todo pkg punk::config |
||||
|
||||
#defaults |
||||
dict set startup exec_unknown true ;#whether to use exec instead of experimental shellfilter::run |
||||
dict set startup color_stdout [list cyan bold] |
||||
dict set startup color_stderr [list red bold] |
||||
dict set startup syslog_stdout "127.0.0.1:514" |
||||
dict set startup syslog_stderr "127.0.0.1:514" |
||||
#default file logs to logs folder at same location as exe if writable, or empty string |
||||
dict set startup logfile_stdout "" |
||||
dict set startup logfile_stderr "" |
||||
set exefolder [file dirname [info nameofexecutable]] |
||||
set log_folder $exefolder/logs |
||||
dict set startup scriptlib $exefolder/scriptlib |
||||
if {[file exists $log_folder]} { |
||||
if {[file isdirectory $log_folder] && [file writable $log_folder]} { |
||||
dict set startup logfile_stdout $log_folder/repl-exec-stdout.txt |
||||
dict set startup logfile_stderr $log_folder/repl-exec-stderr.txt |
||||
} |
||||
} |
||||
|
||||
|
||||
#todo - load/write config file |
||||
|
||||
#env vars override the configuration |
||||
|
||||
#todo - define which configvars are settable in env |
||||
set known_punk_env_vars [list \ |
||||
PUNK_SCRIPTLIB \ |
||||
PUNK_EXECUNKNOWN \ |
||||
PUNK_COLOR_STDERR \ |
||||
PUNK_COLOR_STDOUT \ |
||||
PUNK_LOGFILE_STDOUT \ |
||||
PUNK_LOGFILE_STDERR \ |
||||
PUNK_SYSLOG_STDOUT \ |
||||
PUNK_SYSLOG_STDERR \ |
||||
] |
||||
|
||||
#override with env vars if set |
||||
foreach evar $known_punk_env_vars { |
||||
if {[info exists ::env($evar)]} { |
||||
set f [set ::env($evar)] |
||||
if {$f ne "default"} { |
||||
#e.g PUNK_SCRIPTLIB -> scriptlib |
||||
set varname [string tolower [string range $evar 5 end]] |
||||
dict set startup $varname $f |
||||
} |
||||
} |
||||
} |
||||
|
||||
set running [dict create] |
||||
set running [dict merge $running $startup] |
||||
} |
||||
|
||||
namespace eval punk { |
||||
proc scriptlibpath {{shortname {}} args} { |
||||
upvar ::punk::config::running running_config |
||||
set scriptlib [dict get $running_config scriptlib] |
||||
if {[string match "lib::*" $shortname]} { |
||||
set relpath [string map [list "lib::" "" "::" "/"] $shortname] |
||||
set relpath [string trimleft $relpath "/"] |
||||
set fullpath $scriptlib/$relpath |
||||
} else { |
||||
set shortname [string trimleft $shortname "/"] |
||||
set fullpath $scriptlib/$shortname |
||||
} |
||||
return $fullpath |
||||
} |
||||
|
||||
#todo - something better - 'previous' rather than reverting to startup |
||||
proc channelcolors {{onoff {}}} { |
||||
upvar ::punk::config::running running_config |
||||
upvar ::punk::config::startup startup_config |
||||
|
||||
if {![string length $onoff]} { |
||||
return [list stdout [dict get $running_config color_stdout] stderr [dict get $running_config color_stderr]] |
||||
} else { |
||||
set lower_onoff [string tolower $onoff] |
||||
if {$lower_onoff in [list true on 1]} { |
||||
dict set running_config color_stdout [dict get $startup_config color_stdout] |
||||
dict set running_config color_stderr [dict get $startup_config color_stderr] |
||||
} elseif {$lower_onoff in [list false off 0]} { |
||||
dict set running_config color_stdout "" |
||||
dict set running_config color_stderr "" |
||||
} else { |
||||
error "channelcolors: invalid value $onoff - expected true|false|on|off|1|0" |
||||
} |
||||
} |
||||
return [list stdout [dict get $running_config color_stdout] stderr [dict get $running_config color_stderr]] |
||||
} |
||||
#useful for aliases e.g treemore -> xmore tree |
||||
proc xmore {args} { |
||||
{*}$args | more |
||||
} |
||||
proc winpath {path} { |
||||
#convert /c/etc to C:/etc |
||||
set re {^/([[:alpha:]]){1}/.*} |
||||
|
||||
set volumes [file volumes] |
||||
#exclude things like //zipfs:/ |
||||
set driveletters [list] |
||||
foreach v $volumes { |
||||
if {[regexp {^([[:alpha:]]){1}:/$} $v _ letter]} { |
||||
lappend driveletters $letter |
||||
} |
||||
} |
||||
#puts stderr "->$driveletters" |
||||
if {[regexp $re $path _ letter]} { |
||||
#upper case appears to be windows canonical form |
||||
if {[string toupper $letter] in $driveletters} { |
||||
set path [string toupper $letter]:/[string range $path 3 end] |
||||
} |
||||
} elseif {[regexp {^/mnt|MNT/([[:alpha:]]){1}/.*} $path _ letter]} { |
||||
if {[string toupper $letter] in $driveletters} { |
||||
set path [string toupper $letter]:/[string range $path 7 end] |
||||
} |
||||
} |
||||
#puts stderr "=> $path" |
||||
#things like 'which' seem to return a path minus the .exe - so we'll just test the containing folder |
||||
if {![file exists [file dirname $path]]} { |
||||
set path [file normalize $path] |
||||
} |
||||
return $path |
||||
} |
||||
proc windir {path} { |
||||
return [file dirname [punk::winpath $path]] |
||||
} |
||||
|
||||
#------------------------------------------------------------------- |
||||
#sh 'test' equivalent - to be used with exitcode of process |
||||
# |
||||
|
||||
#single evaluation to get exitcode |
||||
proc sh_test {args} { |
||||
tailcall run test {*}$args |
||||
} |
||||
|
||||
|
||||
#double-evaluation to get true/fals |
||||
#faster tcl equivalents where possible to accuratley provide, and fallthrough to sh for compatibility of unimplemented |
||||
#The problem with fallthrough is that sh/bash etc have a different view of existant files |
||||
#e.g unix files such as /dev/null vs windows devices such as CON,PRN |
||||
#e.g COM1 is mapped as /dev/ttyS1 in wsl (?) |
||||
proc sh_TEST {args} { |
||||
set a1 [lindex $args 0] |
||||
set a2 [lindex $args 1] |
||||
set a3 [lindex $args 2] |
||||
if {[llength $args] == 1} { |
||||
#equivalent of -n STRING |
||||
return [expr {[string length $a1] != 0}] |
||||
} elseif {[llength $args] == 2} { |
||||
switch -- $a1 { |
||||
-b { |
||||
#dubious utility on FreeBSD, windows? |
||||
#FreeBSD has dropped support for block devices - stating 'No serious applications rely on block devices' |
||||
#Linux apparently uses them though |
||||
if{[file exists $a2]} { |
||||
if {[file type $a2] eq "blockSpecial"} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-c { |
||||
#e.g on windows CON,NUL |
||||
if {[file exists $a2]} { |
||||
if {[file type $a2] eq "characterSpecial"} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-d { |
||||
return [file isdirectory $a2] |
||||
} |
||||
-e { |
||||
return [file exists $a2] |
||||
} |
||||
-f { |
||||
#e.g on windows CON,NUL |
||||
if {[file exists $a2]} { |
||||
if {[file type $a2] eq "file"} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-h - |
||||
-L { |
||||
return [expr {[file type $a2] eq "link"}] |
||||
} |
||||
-s { |
||||
if {[file exists $a2] && ([file size $a2] > 0 )} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-S { |
||||
if {[file exists $a2]} { |
||||
if {[file type $a2] eq "socket"} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-x { |
||||
if {[file exists $a2] && [file executable $a2]} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-w { |
||||
if {[file exists $a2] && [file writable $a2]} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
-z { |
||||
return [expr {[string length $a2] == 0}] |
||||
} |
||||
-n { |
||||
return [expr {[string length $a2] != 0}] |
||||
} |
||||
default { |
||||
tailcall apply {arglist {uplevel #0 [run test {*}$arglist]} ::} $args |
||||
} |
||||
} |
||||
} elseif {[llength $args] == 3} { |
||||
switch -- $a2 { |
||||
"=" { |
||||
return [string equal $a1 $a3] |
||||
} |
||||
"!=" { |
||||
return [expr {$a1 ne $a3}] |
||||
} |
||||
"-eq" { |
||||
if {![string is integer -strict $a1]} { |
||||
puts stderr "sh_TEST: invalid integer '$a1'" |
||||
return false |
||||
} |
||||
if {![string is integer -strict $a3]} { |
||||
puts stderr "sh_TEST: invalid integer '$a3'" |
||||
return false |
||||
} |
||||
return [expr {$a1 == $a3}] |
||||
} |
||||
"-ge" { |
||||
return [expr {$a1 >= $a3}] |
||||
} |
||||
"-gt" { |
||||
return [expr {$a1 > $a3}] |
||||
} |
||||
"-le" { |
||||
return [expr {$a1 <= $a3}] |
||||
} |
||||
"-lt" { |
||||
return [expr {$a1 < $a3}] |
||||
} |
||||
"-ne" { |
||||
return [expr {$a1 != $a3}] |
||||
} |
||||
default { |
||||
tailcall apply {arglist {uplevel #0 [run test {*}$arglist]} ::} $args |
||||
} |
||||
} |
||||
} else { |
||||
tailcall apply {arglist {uplevel #0 [run test {*}$arglist]} ::} $args |
||||
} |
||||
} |
||||
proc sh_echo {args} { |
||||
tailcall run echo {*}$args |
||||
} |
||||
proc sh_ECHO {args} { |
||||
tailcall apply {arglist {uplevel #0 [run echo {*}$arglist]} ::} $args |
||||
} |
||||
|
||||
|
||||
#sh style true/false for process exitcode. 0 is true - everything else false |
||||
proc exitcode {c} { |
||||
if {[string is integer -strict $c]} { |
||||
#return [expr {$c == 0}] |
||||
#return true/false to make it clearer we are outputting tcl-boolean inverse mapping from the shell style 0=true |
||||
if {$c == 0} { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
#------------------------------------------------------------------- |
||||
|
||||
namespace export help aliases alias cdwin cdwindir winpath windir |
||||
namespace ensemble create |
||||
|
||||
#tailcall is important |
||||
#TODO - fix. conflicts with Tk toplevel command "." |
||||
proc ./ {args} { |
||||
set ::punk::last_run_display [list] |
||||
|
||||
if {([llength $args]) && ([lindex $args 0] eq "")} { |
||||
set args [lrange $args 1 end] |
||||
} |
||||
|
||||
|
||||
if {![llength $args]} { |
||||
set out [runout -n ls -aFC] |
||||
#puts stdout $out |
||||
#puts stderr [a+ white]$out[a+] |
||||
set result [pwd] |
||||
set chunklist [list] |
||||
lappend chunklist [list stderr "[a+ white light]$out[a+]\n"] |
||||
lappend chunklist [list stdout $result\n] |
||||
set ::punk::last_run_display $chunklist |
||||
return $result |
||||
} else { |
||||
set a1 [lindex $args 0] |
||||
if {$a1 in [list . ..]} { |
||||
if {$a1 eq ".."} { |
||||
cd $a1 |
||||
} |
||||
tailcall punk::./ {*}[lrange $args 1 end] |
||||
} |
||||
set curdir [pwd] |
||||
set path $curdir/$a1 |
||||
if {[file type $path] eq "file"} { |
||||
if {[string tolower [file extension $path]] in [list ".tcl" ".tm"]} { |
||||
set newargs [lrange $args 1 end] |
||||
set ::argv0 $path |
||||
set ::argc [llength $newargs] |
||||
set ::argv $newargs |
||||
tailcall source $path |
||||
} else { |
||||
puts stderr "Cannot run [file extension $path] file directly ([file tail $path])" |
||||
return [pwd] |
||||
} |
||||
} |
||||
if {[file type $path] eq "directory"} { |
||||
cd $path |
||||
tailcall punk::./ {*}[lrange $args 1 end] |
||||
} |
||||
error "Cannot access path $path" |
||||
} |
||||
} |
||||
proc ../ {args} { |
||||
set ::punk::last_run_display |
||||
if {![llength $args]} { |
||||
cd .. |
||||
} else { |
||||
cd ../[file join {*}$args] |
||||
} |
||||
set out [runout -n ls -aFC] |
||||
set result [pwd] |
||||
#return $out\n[pwd] |
||||
set chunklist [list] |
||||
lappend chunklist [list stderr "[a+ white light]$out[a+]\n"] |
||||
lappend chunklist [list stdout $result\n] |
||||
set ::punk::last_run_display $chunklist |
||||
return $result |
||||
} |
||||
proc ls {args} { |
||||
if {![llength $args]} { |
||||
set args [list [pwd]] |
||||
} |
||||
if {[llength $args] ==1} { |
||||
return [glob -nocomplain -tails -dir [lindex $args 0] *] |
||||
} else { |
||||
set result [dict create] |
||||
foreach a $args { |
||||
set k [file normalize $a] |
||||
set contents [glob -nocomplain -tails -dir $a *] |
||||
dict set result $k $contents |
||||
} |
||||
return $result |
||||
} |
||||
} |
||||
proc cdwin {path} { |
||||
set path [punk::winpath $path] |
||||
cd $path |
||||
} |
||||
proc cdwindir {path} { |
||||
set path [punk::winpath $path] |
||||
cd [file dirname $path] |
||||
} |
||||
|
||||
#return list of {chan chunk} elements |
||||
proc help_chunks {} { |
||||
set chunks [list] |
||||
set linesep [string repeat - 76] |
||||
catch { |
||||
package require patternpunk |
||||
#puts -nonewline stderr [>punk . rhs] |
||||
lappend chunks [list stderr [>punk . rhs]] |
||||
} |
||||
set text "" |
||||
set known $::punk::config::known_punk_env_vars |
||||
append text $linesep\n |
||||
append text "punk environment vars:\n" |
||||
append text $linesep\n |
||||
set col1 [string repeat " " 25] |
||||
set col2 [string repeat " " 50] |
||||
foreach v $known { |
||||
set c1 [overtype::left $col1 $v] |
||||
if {[info exists ::env($v)]} { |
||||
set c2 [overtype::left $col2 [set ::env($v)] |
||||
} else { |
||||
set c2 [overtype::right $col2 "(NOT SET)"] |
||||
} |
||||
append text "$c1 $c2\n" |
||||
} |
||||
append text $linesep\n |
||||
lappend chunks [list stdout $text] |
||||
|
||||
set text "" |
||||
append text "Punk commands:\n" |
||||
append text "punk help\n" |
||||
lappend chunks [list stdout $text] |
||||
return $chunks |
||||
} |
||||
proc help {} { |
||||
set chunks [help_chunks] |
||||
foreach chunk $chunks { |
||||
lassign $chunk chan text |
||||
puts -nonewline $chan $text |
||||
} |
||||
} |
||||
#current interp aliases except those created by pattern package '::p::*' |
||||
proc aliases {{glob *}} { |
||||
set interesting [lmap a [interp aliases ""] {expr {![string match ::p::* $a] ? $a : [continue]}}] |
||||
set matched [lsearch -all -inline $interesting $glob] |
||||
} |
||||
proc alias {a args} { |
||||
if {[llength $args]} { |
||||
if {$a in [interp aliases ""]} { |
||||
set existing [interp alias "" $a] |
||||
puts stderr "Overwriting existing alias $a -> $existing with $a -> $args (in current session only)" |
||||
} |
||||
interp alias "" $a "" {*}$args |
||||
} else { |
||||
return [interp alias "" $a] |
||||
} |
||||
} |
||||
|
||||
#sh style 'test' and 'exitcode' (0 is false) |
||||
interp alias {} sh_test {} punk::sh_test |
||||
interp alias {} sh_echo {} punk::sh_echo |
||||
interp alias {} sh_TEST {} punk::sh_TEST |
||||
interp alias {} sh_ECHO {} punk::sh_ECHO |
||||
|
||||
|
||||
interp alias {} exitcode {} punk::exitcode |
||||
|
||||
|
||||
#friendly sh aliases (which user may wish to disable e.g if conflicts) |
||||
interp alias {} test {} punk::sh_test ;#not much reason to run 'test' directly in punk shell (or tclsh shell) as returncode not obvious anyway due to use of exec |
||||
interp alias {} TEST {} punk::sh_TEST; #double-evaluation to return tcl true/false from exitcode |
||||
interp alias {} echo {} punk::sh_echo |
||||
interp alias {} ECHO {} punk::sh_ECHO |
||||
|
||||
interp alias {} c {} clear |
||||
interp alias {} a+ {} shellfilter::ansi::+ |
||||
interp alias {} run {} shellrun::run |
||||
interp alias {} runout {} shellrun::runout |
||||
interp alias {} runerr {} shellrun::runerr |
||||
interp alias {} runx {} shellrun::runx |
||||
|
||||
interp alias {} help {} punk help |
||||
interp alias {} aliases {} punk aliases |
||||
interp alias {} alias {} punk alias |
||||
interp alias {} treemore {} punk::xmore tree |
||||
#---------------------------------------------- |
||||
#leave the winpath related aliases available on all platforms |
||||
interp alias {} cdwin {} punk cdwin |
||||
interp alias {} cdwindir {} punk cdwindir |
||||
interp alias {} winpath {} punk winpath |
||||
interp alias {} windir {} punk windir |
||||
#---------------------------------------------- |
||||
#git |
||||
interp alias {} gs {} git status |
||||
interp alias {} gl {} git log --oneline --decorate ;#decorate so stdout consistent with what we see on console |
||||
|
||||
#---------------------------------------------- |
||||
interp alias {} l {} ls -aFC ;#wide listing () |
||||
interp alias {} ll {} ls -laFo --color=always |
||||
interp alias {} lw {} ls -aFv --color=always |
||||
interp alias {} ./ {} punk::./ |
||||
interp alias {} ../ {} punk::../ |
||||
if {$::tcl_platform(platform) eq "windows"} { |
||||
set has_powershell 1 |
||||
interp alias {} dl {} dir /q |
||||
interp alias {} dw {} dir /W/D |
||||
} else { |
||||
#todo - natsorted equivalent |
||||
#interp alias {} dl {} |
||||
#todo - powershell detection on other platforms |
||||
set has_powershell 0 |
||||
} |
||||
if {$has_powershell} { |
||||
interp alias {} psls {} pwsh -nop -nolo -c ls |
||||
interp alias {} psps {} pwsh -nop -nolo -c ps |
||||
} |
||||
|
||||
} |
@ -0,0 +1,377 @@
|
||||
# vim: set ft=tcl |
||||
# |
||||
package provide shellrun [namespace eval shellrun { |
||||
variable version |
||||
set version 0.1 |
||||
}] |
||||
#purpose: handle the run commands that call shellfilter::run |
||||
#e.g run,runout,runerr,runx |
||||
|
||||
|
||||
|
||||
#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 |
||||
|
||||
|
||||
proc run {args} { |
||||
set ::punk::last_run_display [list] |
||||
#we provide -nonewline 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"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "run: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
if {"-nonewline" in $runopts} { |
||||
set nonewline 1 |
||||
} else { |
||||
set nonewline 0 |
||||
} |
||||
|
||||
set id_err [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}] |
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none ] |
||||
shellfilter::stack::remove stderr $id_err |
||||
|
||||
flush stderr |
||||
flush stdout |
||||
|
||||
set c [shellfilter::ansi::+ green] |
||||
set n [shellfilter::ansi::+] |
||||
if {[dict exists $exitinfo error]} { |
||||
error [dict get $exitinfo error] |
||||
} |
||||
|
||||
return $exitinfo |
||||
} |
||||
|
||||
proc runout {args} { |
||||
set ::punk::last_run_display [list] |
||||
variable runout |
||||
variable runerr |
||||
set runout "" |
||||
set runerr "" |
||||
|
||||
set known_runopts [list "-echo" "-e" "-nonewline" "-n"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runout: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
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}] |
||||
} |
||||
|
||||
#shellfilter::run [lrange $args 1 end] -teehandle punk -outchan stdout -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler |
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -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]} { |
||||
#we must raise an error. |
||||
#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] |
||||
} |
||||
} else { |
||||
set c [a+ Yellow red bold] |
||||
} |
||||
#exitcode not part of return value for runout - colourcode appropriately |
||||
lappend chunklist [list stderr "$c$exitinfo$n\n"] |
||||
|
||||
|
||||
set chunk "[a+ red bold]stderr[a+]\n" |
||||
if {[string length $::shellrun::runerr]} { |
||||
if {$nonewline} { |
||||
set e [string trimright $::shellrun::runerr \r\n] |
||||
} else { |
||||
set e $::shellrun::runerr |
||||
} |
||||
append chunk "$e\n" |
||||
} |
||||
lappend chunklist [list stderr $chunk] |
||||
|
||||
|
||||
|
||||
|
||||
lappend chunklist [list stderr "[a+ white bold]stdout[a+]\n"] |
||||
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\n" ;#this newline is the display output separator - always there whether data has trailing newline or not. |
||||
} |
||||
lappend chunklist [list stdout $chunk] |
||||
|
||||
|
||||
set ::punk::last_run_display $chunklist |
||||
|
||||
if {$nonewline} { |
||||
return [string trimright $::shellrun::runout \r\n] |
||||
} else { |
||||
return $::shellrun::runout |
||||
} |
||||
} |
||||
|
||||
proc runerr {args} { |
||||
set ::punk::last_run_display [list] |
||||
variable runout |
||||
variable runerr |
||||
set runout "" |
||||
set runerr "" |
||||
set known_runopts [list "-echo" "-e" "-nonewline" "-n"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runerr: Unknown runoption $o" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
if {"-nonewline" in $runopts} { |
||||
set nonewline 1 |
||||
} else { |
||||
set nonewline 0 |
||||
} |
||||
|
||||
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 -teehandle punk -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]} { |
||||
#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] |
||||
} |
||||
} else { |
||||
set c [a+ Yellow red bold] |
||||
} |
||||
#exitcode not part of return value for runout - colourcode appropriately |
||||
lappend chunklist [list stderr "$c$exitinfo$n\n"] |
||||
|
||||
|
||||
lappend chunklist [list stderr "[a+ white bold]stdout[a+]\n"] |
||||
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\n" ;#this newline is the display output separator - always there whether data has trailing newline or not. |
||||
} |
||||
lappend chunklist [list stdout $chunk] |
||||
|
||||
|
||||
|
||||
set chunk "[a+ red bold]stderr[a+]\n" |
||||
if {[string length $::shellrun::runerr]} { |
||||
if {$nonewline} { |
||||
set e [string trimright $::shellrun::runerr \r\n] |
||||
} else { |
||||
set e $::shellrun::runerr |
||||
} |
||||
append chunk "$e\n" |
||||
} |
||||
lappend chunklist [list stderr $chunk] |
||||
|
||||
|
||||
set ::punk::last_run_display $chunklist |
||||
|
||||
if {$nonewline} { |
||||
return [string trimright $::shellrun::runerr \r\n] |
||||
} |
||||
return $::shellrun::runerr |
||||
} |
||||
|
||||
proc runx {args} { |
||||
set ::punk::last_run_display [list] |
||||
variable last_run_display |
||||
variable runout |
||||
variable runerr |
||||
set runout "" |
||||
set runerr "" |
||||
|
||||
set known_runopts [list "-echo" "-e" "-nonewline" "-n"] |
||||
set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline"] ;#include map to self |
||||
set runopts [list] |
||||
set cmdargs [list] |
||||
set idx_first_cmdarg [lsearch -not $args "-*"] |
||||
set runopts [lrange $args 0 $idx_first_cmdarg-1] |
||||
set cmdargs [lrange $args $idx_first_cmdarg end] |
||||
foreach o $runopts { |
||||
if {$o ni $known_runopts} { |
||||
error "runx: Unknown runoption $o - known options $known_runopts" |
||||
} |
||||
} |
||||
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||
if {"-nonewline" in $runopts} { |
||||
set nonewline 1 |
||||
} else { |
||||
set nonewline 0 |
||||
} |
||||
|
||||
|
||||
|
||||
#shellfilter::stack::remove stdout $::repl::id_outstack |
||||
|
||||
if {"-echo" in $runopts} { |
||||
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action sink-locked -settings {-varname ::shellrun::runerr}] |
||||
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action sink-locked -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 exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||
set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none] |
||||
|
||||
shellfilter::stack::remove stdout $stdout_stackid |
||||
shellfilter::stack::remove stderr $stderr_stackid |
||||
|
||||
|
||||
flush stderr |
||||
flush stdout |
||||
|
||||
|
||||
#set x [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::repl::runxoutput}] |
||||
set chunklist [list] |
||||
lappend chunklist [list stderr "[a+ white bold]stdout[a+]\n"] |
||||
|
||||
set chunk "" |
||||
if {[string length $::shellrun::runout]} { |
||||
append chunk "$::shellrun::runout\n" |
||||
} |
||||
lappend chunklist [list stdout $chunk] |
||||
|
||||
|
||||
set chunk "[a+ red bold]stderr[a+]\n" |
||||
if {[string length $::shellrun::runerr]} { |
||||
append chunk "$::shellrun::runerr\n" |
||||
} |
||||
lappend chunklist [list stderr $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+ white bold] |
||||
} |
||||
} |
||||
lappend chunklist [list stderr "$c$exitinfo$n\n"] |
||||
|
||||
set ::punk::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] |
||||
|
||||
|
||||
if {[dict exists $exitinfo error]} { |
||||
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||
error [dict get $exitinfo error] |
||||
} |
||||
if {$nonewline} { |
||||
return [list stdout [string trimright $::shellrun::runout \r\n] stderr [string trimright $::shellrun::runerr \r\n] {*}$exitinfo] |
||||
} |
||||
return [list stdout $::shellrun::runout stderr $::shellrun::runerr {*}$exitinfo] |
||||
} |
||||
} |
Loading…
Reference in new issue