Fix some macOS issues
[llpp.git] / utils.ml
blob8dbbae78a9297670f969e63f6c9fff7dc1c48091
1 exception Quit;;
3 external measurestr : int -> string -> float = "ml_measure_string";;
5 module E = struct
6 let s = "";;
7 let b = Bytes.empty;;
8 let a = [||];;
9 end;;
11 type platform = | Punknown | Plinux | Pmacos | Pbsd;;
13 let asciilower = let auld = Char.code 'A' - Char.code 'a' in
14 function
15 | ('A'..'Z') as c -> Char.code c - auld |> Char.chr
16 | c -> c
19 let tempfailureretry f a =
20 let rec g () =
21 try f a with Unix.Unix_error (Unix.EINTR, _, _) -> g ()
22 in g ()
25 external cloexec : Unix.file_descr -> unit = "ml_cloexec";;
26 external hasdata : Unix.file_descr -> bool = "ml_hasdata";;
27 external toutf8 : int -> string = "ml_keysymtoutf8";;
28 external mbtoutf8 : string -> string = "ml_mbtoutf8";;
29 external spawn : string -> (Unix.file_descr * int) list -> int = "ml_spawn";;
30 external platform : unit -> (platform * string array) = "ml_platform";;
32 let now = Unix.gettimeofday;;
33 let platform, uname = platform ();;
34 let dolog fmt = Format.ksprintf prerr_endline fmt;;
36 let exntos = function
37 | Unix.Unix_error (e, s, a) ->
38 Printf.sprintf "%s(%s) : %s (%d)"
39 s a (Unix.error_message e) (Obj.magic e)
40 | exn -> Printexc.to_string exn
43 let error fmt = Printf.kprintf (fun s -> failwith s) fmt;;
45 module IntSet = Set.Make (struct type t = int let compare = (-) end);;
47 let emptystr s = String.length s = 0;;
48 let nonemptystr s = String.length s > 0;;
49 let bound v minv maxv = max minv (min maxv v);;
51 module Opaque : sig
52 type t = private string
53 val of_string : string -> t
54 val to_string : t -> string
55 end = struct
56 type t = string
57 let of_string s = s
58 let to_string t = t
59 end
62 let (~<) = Opaque.of_string;;
63 let (~>) = Opaque.to_string;;
65 let int_of_string_with_suffix s =
66 let l = String.length s in
67 let s1, shift =
68 if l > 1
69 then
70 let p = l-1 in
71 match s.[p] with
72 | 'k' | 'K' -> String.sub s 0 p, 10
73 | 'm' | 'M' -> String.sub s 0 p, 20
74 | 'g' | 'G' -> String.sub s 0 p, 30
75 | _ -> s, 0
76 else s, 0
78 let n = int_of_string s1 in
79 let m = n lsl shift in
80 if m < 0 || m < n
81 then error "value too large"
82 else m
85 let string_with_suffix_of_int n =
86 if n = 0
87 then "0"
88 else
89 let units = [(30, "G"); (20, "M"); (10, "K")] in
90 let prettyint n =
91 let rec loop s n =
92 let h = n mod 1000 in
93 let n = n / 1000 in
94 if n = 0
95 then string_of_int h ^ s
96 else (
97 let s = Printf.sprintf "_%03d%s" h s in
98 loop s n
101 loop E.s n
103 let rec find = function
104 | [] -> prettyint n
105 | (shift, suffix) :: rest ->
106 if (n land ((1 lsl shift) - 1)) = 0
107 then prettyint (n lsr shift) ^ suffix
108 else find rest
110 find units
113 let color_of_string s =
114 Scanf.sscanf s "%d/%d/%d" (fun r g b ->
115 (float r /. 255.0, float g /. 255.0, float b /. 255.0)
119 let rgba_of_string s =
120 Scanf.sscanf
121 s "%d/%d/%d/%d" (fun r g b a ->
122 (float r /. 255.0, float g /. 255.0, float b /. 255.0, float a /. 255.0)
126 let color_to_string (r, g, b) =
127 let r = truncate (r *. 255.0)
128 and g = truncate (g *. 255.0)
129 and b = truncate (b *. 255.0) in
130 Printf.sprintf "%d/%d/%d" r g b
133 let rgba_to_string (r, g, b, a) =
134 let r = truncate (r *. 255.0)
135 and g = truncate (g *. 255.0)
136 and b = truncate (b *. 255.0)
137 and a = truncate (a *. 255.0) in
138 Printf.sprintf "%d/%d/%d/%d" r g b a
141 let abspath path =
142 if Filename.is_relative path
143 then
144 let cwd = Sys.getcwd () in
145 if Filename.is_implicit path
146 then Filename.concat cwd path
147 else Filename.concat cwd (Filename.basename path)
148 else
149 path
152 module Ne = struct
153 let index s c =
154 try String.index s c
155 with Not_found -> -1
157 let clo fd f =
158 try tempfailureretry Unix.close fd
159 with exn -> f @@ exntos exn
161 end;;
163 let getenvwithdef name def =
164 match Sys.getenv name with
165 | env -> env
166 | exception Not_found -> def
169 module Re = struct
170 let crlf = Str.regexp "[\r\n]";;
171 let percent = Str.regexp "%s";;
172 let whitespace = Str.regexp "[ \t]";;
173 end;;
175 let unit () = ();;
177 let addchar s c =
178 let b = Buffer.create (String.length s + 1) in
179 Buffer.add_string b s;
180 Buffer.add_char b c;
181 Buffer.contents b;
184 let btod b = if b then 1 else 0;;
186 let splitatchar s c = let open String in
187 match index s c with
188 | pos -> sub s 0 pos, sub s (pos+1) (length s - pos - 1)
189 | exception Not_found -> s, E.s
192 let boundastep h step =
193 if step < 0
194 then bound step ~-h 0
195 else bound step 0 h
198 let withoutlastutf8 s =
199 let len = String.length s in
200 if len = 0
201 then s
202 else
203 let rec find pos =
204 if pos = 0
205 then pos
206 else
207 let b = Char.code s.[pos] in
208 if b land 0b11000000 = 0b11000000
209 then pos
210 else find (pos-1)
212 let first =
213 if Char.code s.[len-1] land 0x80 = 0
214 then len-1
215 else find (len-1)
217 String.sub s 0 first;
220 let fdcontents fd =
221 let l = 4096 in
222 let b = Buffer.create l in
223 let s = Bytes.create l in
224 let rec loop () =
225 let n = tempfailureretry (Unix.read fd s 0) l in
226 if n = 0
227 then Buffer.contents b
228 else (
229 Buffer.add_subbytes b s 0 n;
230 loop ()
233 loop ()
236 let filecontents path =
237 let fd = Unix.openfile path [Unix.O_RDONLY] 0o0 in
238 match fdcontents fd with
239 | exception exn ->
240 error "failed to read contents of %s: %s" path @@ exntos exn
241 | s ->
242 Ne.clo fd @@ error "failed to close descriptor for %s: %s" path;
246 let getcmdoutput errfun cmd =
247 let reperror fmt = Printf.kprintf errfun fmt in
248 let clofail s e = error "failed to close %s: %s" s e in
249 match Unix.pipe () with
250 | exception exn ->
251 reperror "pipe failed: %s" @@ exntos exn;
253 | (r, w) ->
254 match spawn cmd [r, -1; w, 1] with
255 | exception exn ->
256 reperror "failed to execute %S: %s" cmd @@ exntos exn;
258 | pid ->
259 Ne.clo w @@ clofail "write end of the pipe";
260 let s =
261 match Unix.waitpid [] pid with
262 | exception exn ->
263 reperror "waitpid on %S %d failed: %s" cmd pid @@ exntos exn;
265 | _pid, Unix.WEXITED 0 ->
266 begin
267 match fdcontents r with
268 | exception exn ->
269 reperror "failed to read output of %S: %s" cmd @@ exntos exn;
271 | s ->
272 let l = String.length s in
273 if l > 0 && s.[l-1] = '\n'
274 then String.sub s 0 (l-1)
275 else s
276 end;
277 | _pid, Unix.WEXITED n ->
278 reperror "%S exited with error code %d" cmd n;
280 | _pid, Unix.WSIGNALED n ->
281 reperror "%S was killed with signal %d" cmd n;
283 | _pid, Unix.WSTOPPED n ->
284 reperror "%S was stopped by signal %d" cmd n;
287 Ne.clo r @@ clofail "read end of the pipe";
291 let geturl =
292 let re = Str.regexp {|.*\(\(https?\|ftp\|mailto\|file\)://[^ ]+\).*|} in
293 fun s ->
294 if Str.string_match re s 0
295 then Str.matched_group 1 s
296 else E.s
299 let substratis s pos subs =
300 let subslen = String.length subs in
301 if String.length s - pos >= subslen
302 then
303 let rec cmp i = i = subslen || (s.[pos+i] = subs.[i]) && cmp (i+1)
304 in cmp 0
305 else false
308 let w8 s pos i = Bytes.set s pos (Char.chr (i land 0xff));;
309 let r8 s pos = Char.code (Bytes.get s pos);;
311 let w16 s pos i =
312 w8 s pos i;
313 w8 s (pos+1) (i lsr 8)
316 let w32 s pos i =
317 w16 s pos i;
318 w16 s (pos+2) (i lsr 16)
321 let r16 s pos =
322 let rb pos1 = Char.code (Bytes.get s (pos + pos1)) in
323 (rb 0) lor ((rb 1) lsl 8)
326 let r16s s pos =
327 let i = r16 s pos in
328 i - ((i land 0x8000) lsl 1)
331 let r32 s pos =
332 let rb pos1 = Char.code (Bytes.get s (pos + pos1)) in
333 let l = (rb 0) lor ((rb 1) lsl 8)
334 and u = (rb 2) lor ((rb 3) lsl 8) in
335 (u lsl 16) lor l
338 let r32s =
339 if Sys.word_size > 32
340 then fun s pos ->
341 let rb pos1 = Char.code (Bytes.get s (pos + pos1)) in
342 let v0 = rb 0 and v1 = rb 1 and v2 = rb 2 and v3 = rb 3 in
343 let v = v0 lor (v1 lsl 8) lor (v2 lsl 16) lor (v3 lsl 24) in
344 if v3 land 0x80 = 0
345 then v
346 else (v - (1 lsl 32))
347 else failwith "r32s: not implemented for word_size <= 32"
350 let vlog fmt = Format.ksprintf ignore fmt;;
352 let pipef ?(closew=true) cap f cmd =
353 match Unix.pipe () with
354 | exception exn -> dolog "%s cannot create pipe: %S" cap @@ exntos exn
355 | (r, w) ->
356 begin match spawn cmd [r, 0; w, -1] with
357 | exception exn -> dolog "%s: cannot execute %S: %s" cap cmd @@ exntos exn
358 | _pid -> f w
359 end;
360 Ne.clo r (dolog "%s failed to close r: %s" cap);
361 if closew then Ne.clo w (dolog "%s failed to close w: %s" cap);
364 let selstring selcmd s =
365 pipef "selstring" (fun w ->
367 let l = String.length s in
368 let bytes = Bytes.unsafe_of_string s in
369 let n = tempfailureretry (Unix.write w bytes 0) l in
370 if n != l
371 then dolog "failed to write %d characters to sel pipe, wrote %d" l n;
372 with exn -> dolog "failed to write to sel pipe: %s" @@ exntos exn
373 ) selcmd