file: Better support for trailing slashes in pathnames
[jimtcl.git] / tclcompat.tcl
blob432c744f0f41d3fb7488fc179fd4eaf565694510
1 # Loads some Tcl-compatible features.
2 # I/O commands, parray, open |..., errorInfo, ::env
3 # try, throw, file copy, file delete -force
5 # (c) 2008 Steve Bennett <steveb@workware.net.au>
8 # Set up the ::env array
9 set env [env]
11 # Provide Tcl-compatible I/O commands
12 if {[info commands stdout] ne ""} {
13 # Tcl-compatible I/O commands
14 foreach p {gets flush close eof seek tell} {
15 proc $p {chan args} {p} {
16 tailcall $chan $p {*}$args
19 unset p
21 # puts is complicated by -nonewline
23 proc puts {{-nonewline {}} {chan stdout} msg} {
24 if {${-nonewline} ni {-nonewline {}}} {
25 tailcall ${-nonewline} puts $msg
27 tailcall $chan puts {*}${-nonewline} $msg
30 # read is complicated by -nonewline
32 # read chan ?maxchars?
33 # read -nonewline chan
34 proc read {{-nonewline {}} chan} {
35 if {${-nonewline} ni {-nonewline {}}} {
36 tailcall ${-nonewline} read {*}${chan}
38 tailcall $chan read {*}${-nonewline}
41 proc fconfigure {f args} {
42 foreach {n v} $args {
43 switch -glob -- $n {
44 -bl* {
45 $f ndelay $(!$v)
47 -bu* {
48 $f buffering $v
50 -tr* {
51 # Just ignore -translation
53 default {
54 return -code error "fconfigure: unknown option $n"
61 # fileevent isn't needed in Jim, but provide it for compatibility
62 proc fileevent {args} {
63 tailcall {*}$args
66 # Second, optional argument is a glob pattern
67 # Third, optional argument is a "putter" function
68 proc parray {arrayname {pattern *} {puts puts}} {
69 upvar $arrayname a
71 set max 0
72 foreach name [array names a $pattern]] {
73 if {[string length $name] > $max} {
74 set max [string length $name]
77 incr max [string length $arrayname]
78 incr max 2
79 foreach name [lsort [array names a $pattern]] {
80 $puts [format "%-${max}s = %s" $arrayname\($name\) $a($name)]
84 # Implements 'file copy' - single file mode only
85 proc {file copy} {{force {}} source target} {
86 try {
87 if {$force ni {{} -force}} {
88 error "bad option \"$force\": should be -force"
91 set in [open $source rb]
93 if {[file exists $target]} {
94 if {$force eq ""} {
95 error "error copying \"$source\" to \"$target\": file already exists"
97 # If source and target are the same, nothing to do
98 if {$source eq $target} {
99 return
101 # Hard linked, or case-insensitive filesystem
102 # Note: mingw returns ino=0 for every file :-(
103 file stat $source ss
104 file stat $target ts
105 if {$ss(dev) == $ts(dev) && $ss(ino) == $ts(ino) && $ss(ino)} {
106 return
109 set out [open $target wb]
110 $in copyto $out
111 $out close
112 } on error {msg opts} {
113 incr opts(-level)
114 return {*}$opts $msg
115 } finally {
116 catch {$in close}
120 # 'open "|..." ?mode?" will invoke this wrapper around exec/pipe
121 # Note that we return a lambda that also provides the 'pid' command
122 proc popen {cmd {mode r}} {
123 lassign [pipe] r w
124 try {
125 if {[string match "w*" $mode]} {
126 lappend cmd <@$r &
127 set pids [exec {*}$cmd]
128 $r close
129 set f $w
130 } else {
131 lappend cmd >@$w &
132 set pids [exec {*}$cmd]
133 $w close
134 set f $r
136 lambda {cmd args} {f pids} {
137 if {$cmd eq "pid"} {
138 return $pids
140 if {$cmd eq "getfd"} {
141 $f getfd
143 if {$cmd eq "close"} {
144 $f close
145 # And wait for the child processes to complete
146 set retopts {}
147 foreach p $pids {
148 lassign [wait $p] status - rc
149 if {$status eq "CHILDSTATUS"} {
150 if {$rc == 0} {
151 continue
153 set msg "child process exited abnormally"
154 } else {
155 set msg "child killed: received signal"
157 set retopts [list -code error -errorcode [list $status $p $rc] $msg]
159 return {*}$retopts
161 tailcall $f $cmd {*}$args
163 } on error {error opts} {
164 $r close
165 $w close
166 error $error
170 # A wrapper around 'pid' that can return the pids for 'popen'
171 local proc pid {{channelId {}}} {
172 if {$channelId eq ""} {
173 tailcall upcall pid
175 if {[catch {$channelId tell}]} {
176 return -code error "can not find channel named \"$channelId\""
178 if {[catch {$channelId pid} pids]} {
179 return ""
181 return $pids
184 # try/on/finally conceptually similar to Tcl 8.6
186 # Usage: try ?catchopts? script ?onclause ...? ?finallyclause?
188 # Where:
189 # catchopts is: options for catch such as -nobreak, -signal
190 # onclause is: on codes {?resultvar? ?optsvar?} script
191 # codes is: a list of return codes (ok, error, etc. or integers), or * for any
192 # finallyclause is: finally script
193 proc try {args} {
194 set catchopts {}
195 while {[string match -* [lindex $args 0]]} {
196 set args [lassign $args opt]
197 if {$opt eq "--"} {
198 break
200 lappend catchopts $opt
202 if {[llength $args] == 0} {
203 return -code error {wrong # args: should be "try ?options? script ?argument ...?"}
205 set args [lassign $args script]
206 set code [catch -eval {*}$catchopts {uplevel 1 $script} msg opts]
208 set handled 0
210 foreach {on codes vars script} $args {
211 switch -- $on \
212 on {
213 if {!$handled && ($codes eq "*" || [info returncode $code] in $codes)} {
214 lassign $vars msgvar optsvar
215 if {$msgvar ne ""} {
216 upvar $msgvar hmsg
217 set hmsg $msg
219 if {$optsvar ne ""} {
220 upvar $optsvar hopts
221 set hopts $opts
223 # Override any body result
224 set code [catch {uplevel 1 $script} msg opts]
225 incr handled
228 finally {
229 set finalcode [catch {uplevel 1 $codes} finalmsg finalopts]
230 if {$finalcode} {
231 # Override any body or handler result
232 set code $finalcode
233 set msg $finalmsg
234 set opts $finalopts
236 break
238 default {
239 return -code error "try: expected 'on' or 'finally', got '$on'"
243 if {$code} {
244 incr opts(-level)
245 return {*}$opts $msg
247 return $msg
250 # Generates an exception with the given code (ok, error, etc. or an integer)
251 # and the given message
252 proc throw {code {msg ""}} {
253 return -code $code $msg
256 # Helper for "file delete -force"
257 proc {file delete force} {path} {
258 foreach e [readdir $path] {
259 file delete -force $path/$e
261 file delete $path