Fix a couple of minor build issues
[jimtcl.git] / tclcompat.tcl
blobfda12348f61d683d686ac36a422dde60f40f0999
1 # (c) 2008 Steve Bennett <steveb@workware.net.au>
3 # Loads some Tcl-compatible features.
4 # I/O commands, case, lassign, parray, errorInfo, ::tcl_platform, ::env
5 # try, throw, file copy, file delete -force
7 # Set up the ::env array
8 set env [env]
10 if {[info commands stdout] ne ""} {
11 # Tcl-compatible I/O commands
12 foreach p {gets flush close eof seek tell} {
13 proc $p {chan args} {p} {
14 tailcall $chan $p {*}$args
17 unset p
19 # puts is complicated by -nonewline
21 proc puts {{-nonewline {}} {chan stdout} msg} {
22 if {${-nonewline} ni {-nonewline {}}} {
23 tailcall ${-nonewline} puts $msg
25 tailcall $chan puts {*}${-nonewline} $msg
28 # read is complicated by -nonewline
30 # read chan ?maxchars?
31 # read -nonewline chan
32 proc read {{-nonewline {}} chan} {
33 if {${-nonewline} ni {-nonewline {}}} {
34 tailcall ${-nonewline} read {*}${chan}
36 tailcall $chan read {*}${-nonewline}
39 proc fconfigure {f args} {
40 foreach {n v} $args {
41 switch -glob -- $n {
42 -bl* {
43 $f ndelay $(!$v)
45 -bu* {
46 $f buffering $v
48 -tr* {
49 # Just ignore -translation
51 default {
52 return -code error "fconfigure: unknown option $n"
59 # case var ?in? pattern action ?pattern action ...?
60 proc case {var args} {
61 # Skip dummy parameter
62 if {[lindex $args 0] eq "in"} {
63 set args [lrange $args 1 end]
66 # Check for single arg form
67 if {[llength $args] == 1} {
68 set args [lindex $args 0]
71 # Check for odd number of args
72 if {[llength $args] % 2 != 0} {
73 return -code error "extra case pattern with no body"
76 # Internal function to match a value agains a list of patterns
77 local proc case.checker {value pattern} {
78 string match $pattern $value
81 foreach {value action} $args {
82 if {$value eq "default"} {
83 set do_action $action
84 continue
85 } elseif {[lsearch -bool -command case.checker $value $var]} {
86 set do_action $action
87 break
91 if {[info exists do_action]} {
92 set rc [catch [list uplevel 1 $do_action] result opts]
93 if {$rc} {
94 incr opts(-level)
96 return {*}$opts $result
100 # fileevent isn't needed in Jim, but provide it for compatibility
101 proc fileevent {args} {
102 tailcall {*}$args
105 # Second, option argument is a glob pattern
106 # Third, optional argument is a "putter" function
108 proc parray {arrayname {pattern *} {puts puts}} {
109 upvar $arrayname a
111 set max 0
112 foreach name [array names a $pattern]] {
113 if {[string length $name] > $max} {
114 set max [string length $name]
117 incr max [string length $arrayname]
118 incr max 2
119 foreach name [lsort [array names a $pattern]] {
120 $puts [format "%-${max}s = %s" $arrayname\($name\) $a($name)]
124 # Implements 'file copy' - single file mode only
125 proc {file copy} {{force {}} source target} {
126 try {
127 if {$force ni {{} -force}} {
128 error "bad option \"$force\": should be -force"
131 set in [open $source]
133 if {[file exists $target]} {
134 if {$force eq ""} {
135 error "error copying \"$source\" to \"$target\": file already exists"
137 # If source and target are the same, nothing to do
138 if {$source eq $target} {
139 return
141 # Hard linked, or case-insensitive filesystem
142 # Note: mingw returns ino=0 for every file :-(
143 file stat $source ss
144 file stat $target ts
145 if {$ss(dev) == $ts(dev) && $ss(ino) == $ts(ino) && $ss(ino)} {
146 return
149 set out [open $target w]
150 $in copyto $out
151 $out close
152 } on error {msg opts} {
153 incr opts(-level)
154 return {*}$opts $msg
155 } finally {
156 catch {$in close}
160 # 'open "|..." ?mode?" will invoke this wrapper around exec/pipe
161 # Note that we return a lambda which also provides the 'pid' command
162 proc popen {cmd {mode r}} {
163 lassign [socket pipe] r w
164 try {
165 if {[string match "w*" $mode]} {
166 lappend cmd <@$r &
167 set pids [exec {*}$cmd]
168 $r close
169 set f $w
170 } else {
171 lappend cmd >@$w &
172 set pids [exec {*}$cmd]
173 $w close
174 set f $r
176 lambda {cmd args} {f pids} {
177 if {$cmd eq "pid"} {
178 return $pids
180 if {$cmd eq "close"} {
181 $f close
182 # And wait for the child processes to complete
183 foreach p $pids { os.wait $p }
184 return
186 tailcall $f $cmd {*}$args
188 } on error {error opts} {
189 $r close
190 $w close
191 error $error
195 # A wrapper around 'pid' which can return the pids for 'popen'
196 local proc pid {{chan {}}} {
197 if {$chan eq ""} {
198 tailcall upcall pid
200 if {[catch {$chan tell}]} {
201 return -code error "can not find channel named \"$chan\""
203 if {[catch {$chan pid} pids]} {
204 return ""
206 return $pids
209 # try/on/finally conceptually similar to Tcl 8.6
211 # Usage: try ?catchopts? script ?onclause ...? ?finallyclause?
213 # Where:
214 # onclause is: on codes {?resultvar? ?optsvar?} script
216 # codes is: a list of return codes (ok, error, etc. or integers), or * for any
218 # finallyclause is: finally script
221 # Where onclause is: on codes {?resultvar? ?optsvar?}
222 proc try {args} {
223 set catchopts {}
224 while {[string match -* [lindex $args 0]]} {
225 set args [lassign $args opt]
226 if {$opt eq "--"} {
227 break
229 lappend catchopts $opt
231 if {[llength $args] == 0} {
232 return -code error {wrong # args: should be "try ?options? script ?argument ...?"}
234 set args [lassign $args script]
235 set code [catch -eval {*}$catchopts [list uplevel 1 $script] msg opts]
237 set handled 0
239 foreach {on codes vars script} $args {
240 switch -- $on \
241 on {
242 if {!$handled && ($codes eq "*" || [info returncode $code] in $codes)} {
243 lassign $vars msgvar optsvar
244 if {$msgvar ne ""} {
245 upvar $msgvar hmsg
246 set hmsg $msg
248 if {$optsvar ne ""} {
249 upvar $optsvar hopts
250 set hopts $opts
252 # Override any body result
253 set code [catch [list uplevel 1 $script] msg opts]
254 incr handled
257 finally {
258 set finalcode [catch [list uplevel 1 $codes] finalmsg finalopts]
259 if {$finalcode} {
260 # Override any body or handler result
261 set code $finalcode
262 set msg $finalmsg
263 set opts $finalopts
265 break
267 default {
268 return -code error "try: expected 'on' or 'finally', got '$on'"
272 if {$code} {
273 incr opts(-level)
274 return {*}$opts $msg
276 return $msg
279 # Generates an exception with the given code (ok, error, etc. or an integer)
280 # and the given message
281 proc throw {code {msg ""}} {
282 return -code $code $msg
285 # Helper for "file delete -force"
286 proc {file delete force} {path} {
287 foreach e [readdir $path] {
288 file delete -force $path/$e
290 file delete $path