Mark tests which require utf-8 support
[jimtcl.git] / tclcompat.tcl
blobd7266e13801544e5cff212c71c5bcf9a871b32be
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, info nameofexecutable
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 default {
49 return -code error "fconfigure: unknown option $n"
56 # case var ?in? pattern action ?pattern action ...?
57 proc case {var args} {
58 # Skip dummy parameter
59 if {[lindex $args 0] eq "in"} {
60 set args [lrange $args 1 end]
63 # Check for single arg form
64 if {[llength $args] == 1} {
65 set args [lindex $args 0]
68 # Check for odd number of args
69 if {[llength $args] % 2 != 0} {
70 return -code error "extra case pattern with no body"
73 # Internal function to match a value agains a list of patterns
74 local proc case.checker {value pattern} {
75 string match $pattern $value
78 foreach {value action} $args {
79 if {$value eq "default"} {
80 set do_action $action
81 continue
82 } elseif {[lsearch -bool -command case.checker $value $var]} {
83 set do_action $action
84 break
88 if {[info exists do_action]} {
89 set rc [catch [list uplevel 1 $do_action] result opts]
90 if {$rc} {
91 incr opts(-level)
93 return {*}$opts $result
97 # fileevent isn't needed in Jim, but provide it for compatibility
98 proc fileevent {args} {
99 tailcall {*}$args
102 # Second, option argument is a glob pattern
103 # Third, optional argument is a "putter" function
105 proc parray {arrayname {pattern *} {puts puts}} {
106 upvar $arrayname a
108 set max 0
109 foreach name [array names a $pattern]] {
110 if {[string length $name] > $max} {
111 set max [string length $name]
114 incr max [string length $arrayname]
115 incr max 2
116 foreach name [lsort [array names a $pattern]] {
117 $puts [format "%-${max}s = %s" $arrayname\($name\) $a($name)]
121 # Implements 'file copy' - single file mode only
122 proc {file copy} {{force {}} source target} {
123 try {
124 if {$force ni {{} -force}} {
125 error "bad option \"$force\": should be -force"
128 set in [open $source]
130 if {$force eq "" && [file exists $target]} {
131 $in close
132 error "error copying \"$source\" to \"$target\": file already exists"
134 set out [open $target w]
135 $in copyto $out
136 $out close
137 } on error {msg opts} {
138 incr opts(-level)
139 return {*}$opts $msg
140 } finally {
141 catch {$in close}
145 # 'open "|..." ?mode?" will invoke this wrapper around exec/pipe
146 # Note that we return a lambda which also provides the 'pid' command
147 proc popen {cmd {mode r}} {
148 lassign [socket pipe] r w
149 try {
150 if {[string match "w*" $mode]} {
151 lappend cmd <@$r &
152 set pids [exec {*}$cmd]
153 $r close
154 set f $w
155 } else {
156 lappend cmd >@$w &
157 set pids [exec {*}$cmd]
158 $w close
159 set f $r
161 lambda {cmd args} {f pids} {
162 if {$cmd eq "pid"} {
163 return $pids
165 if {$cmd eq "close"} {
166 $f close
167 # And wait for the child processes to complete
168 foreach p $pids { os.wait $p }
169 return
171 tailcall $f $cmd {*}$args
173 } on error {error opts} {
174 $r close
175 $w close
176 error $error
180 # A wrapper around 'pid' which can return the pids for 'popen'
181 local proc pid {{chan {}}} {
182 if {$chan eq ""} {
183 tailcall upcall pid
185 if {[catch {$chan tell}]} {
186 return -code error "can not find channel named \"$chan\""
188 if {[catch {$chan pid} pids]} {
189 return ""
191 return $pids
194 # try/on/finally conceptually similar to Tcl 8.6
196 # Usage: try ?catchopts? script ?onclause ...? ?finallyclause?
198 # Where:
199 # onclause is: on codes {?resultvar? ?optsvar?} script
201 # codes is: a list of return codes (ok, error, etc. or integers), or * for any
203 # finallyclause is: finally script
206 # Where onclause is: on codes {?resultvar? ?optsvar?}
207 proc try {args} {
208 set catchopts {}
209 while {[string match -* [lindex $args 0]]} {
210 set args [lassign $args opt]
211 if {$opt eq "--"} {
212 break
214 lappend catchopts $opt
216 if {[llength $args] == 0} {
217 return -code error {wrong # args: should be "try ?options? script ?argument ...?"}
219 set args [lassign $args script]
220 set code [catch -eval {*}$catchopts [list uplevel 1 $script] msg opts]
222 set handled 0
224 foreach {on codes vars script} $args {
225 switch -- $on \
226 on {
227 if {!$handled && ($codes eq "*" || [info returncode $code] in $codes)} {
228 lassign $vars msgvar optsvar
229 if {$msgvar ne ""} {
230 upvar $msgvar hmsg
231 set hmsg $msg
233 if {$optsvar ne ""} {
234 upvar $optsvar hopts
235 set hopts $opts
237 # Override any body result
238 set code [catch [list uplevel 1 $script] msg opts]
239 incr handled
242 finally {
243 set finalcode [catch [list uplevel 1 $codes] finalmsg finalopts]
244 if {$finalcode} {
245 # Override any body or handler result
246 set code $finalcode
247 set msg $finalmsg
248 set opts $finalopts
250 break
252 default {
253 return -code error "try: expected 'on' or 'finally', got '$on'"
257 if {$code} {
258 incr opts(-level)
259 return {*}$opts $msg
261 return $msg
264 # Generates an exception with the given code (ok, error, etc. or an integer)
265 # and the given message
266 proc throw {code {msg ""}} {
267 return -code $code $msg