Move to snippets
[llpp.git] / utils.ml
blob091fe4ceaa9df0d43d1ccbde42c29b8fd9e83d39
1 exception Quit
3 module E = struct
4 let s = ""
5 let b = Bytes.empty
6 let a = [||]
7 let j = (0, 0.0, 0.0)
8 end
10 let tempfailureretry f a =
11 let rec g () =
12 try f a with Unix.Unix_error (Unix.EINTR, _, _) -> g ()
13 in g ()
15 external spawn : string -> (Unix.file_descr * int) list -> int = "ml_spawn"
16 external hasdata : Unix.file_descr -> bool = "ml_hasdata"
18 let now = Unix.gettimeofday
19 let dologf = ref prerr_endline
20 let dolog fmt = Printf.ksprintf !dologf fmt
21 let dolog1 fmt = Printf.ksprintf (fun s -> print_endline s; flush stdout) fmt
23 let exntos = function
24 | Unix.Unix_error (e, s, a) ->
25 Printf.sprintf "%s(%s) : %s (%d)" s a (Unix.error_message e) (Obj.magic e)
26 | exn -> Printexc.to_string exn
28 let onoffs = function | true -> "on" | false -> "off"
30 let error fmt = Printf.kprintf failwith fmt
32 module IntSet = Set.Make (struct type t = int let compare = (-) end)
34 let emptystr s = String.length s = 0
35 let nonemptystr s = String.length s > 0
36 let bound v minv maxv = max minv (min maxv v)
38 module Opaque : sig
39 type t = private string
40 val of_string : string -> t
41 val to_string : t -> string
42 end = struct
43 type t = string
44 let of_string s = s
45 let to_string t = t
46 end
48 let int_of_string_with_suffix s =
49 let l = String.length s in
50 let s1, shift =
51 if l > 1
52 then
53 let p = l-1 in
54 match s.[p] with
55 | 'k' | 'K' -> String.sub s 0 p, 10
56 | 'm' | 'M' -> String.sub s 0 p, 20
57 | 'g' | 'G' -> String.sub s 0 p, 30
58 | _ -> s, 0
59 else s, 0
61 let n = int_of_string s1 in
62 let m = n lsl shift in
63 if m < 0 || m < n
64 then error "value too large"
65 else m
67 let string_with_suffix_of_int n =
68 let rec find = function
69 | [] -> Printf.sprintf "%#d" n
70 | (shift, suffix) :: rest ->
71 if (n land ((1 lsl shift) - 1)) = 0
72 then Printf.sprintf "%#d%c" (n lsr shift) suffix
73 else find rest
75 if n = 0 then "0" else find [(30, 'G'); (20, 'M'); (10, 'K')]
77 let color_of_string s =
78 Scanf.sscanf s "%d/%d/%d" (fun r g b ->
79 (float r /. 255.0, float g /. 255.0, float b /. 255.0)
82 let rgba_of_string s =
83 let c c = float c /. 255.0 in
84 Scanf.sscanf s "%d/%d/%d/%d" (fun r g b a -> c r, c g, c b, c a)
86 let color_to_string (r, g, b) =
87 let c c = c *. 255.0 |> truncate in
88 Printf.sprintf "%d/%d/%d" (c r) (c g) (c b)
90 let rgba_to_string (r, g, b, a) =
91 let c c = c *. 255.0 |> truncate in
92 Printf.sprintf "%d/%d/%d/%d" (c r) (c g) (c b) (c a)
94 let abspath path =
95 if Filename.is_relative path
96 then
97 let cwd = Sys.getcwd () in
98 if Filename.is_implicit path
99 then Filename.concat cwd path
100 else Filename.concat cwd (Filename.basename path)
101 else path
103 module Ne = struct
104 let index s c = try String.index s c with Not_found -> -1
105 let clo fd f =
106 try tempfailureretry Unix.close fd
107 with exn -> f @@ exntos exn
110 let getenvdef name def =
111 match Sys.getenv name with
112 | env -> env
113 | exception Not_found -> def
115 module Re = struct
116 let crlf = Str.regexp "[\r\n]"
117 let percent = Str.regexp "%s"
118 let whitespace = Str.regexp "[ \t]"
121 let addchar s c =
122 let b = Buffer.create (String.length s + 1) in
123 Buffer.add_string b s;
124 Buffer.add_char b c;
125 Buffer.contents b
127 let btod b = if b then 1 else 0
129 let splitatchar s c = let open String in
130 match index s c with
131 | pos -> sub s 0 pos, sub s (pos+1) (length s - pos - 1)
132 | exception Not_found -> s, E.s
134 let boundastep h step =
135 if step < 0
136 then bound step ~-h 0
137 else bound step 0 h
139 let withoutlastutf8 s =
140 let len = String.length s in
141 if len = 0
142 then s
143 else
144 let rec find pos =
145 if pos = 0
146 then pos
147 else
148 let b = Char.code s.[pos] in
149 if b land 0b11000000 = 0b11000000
150 then pos
151 else find (pos-1)
153 let first =
154 if Char.code s.[len-1] land 0x80 = 0
155 then len-1
156 else find (len-1)
158 String.sub s 0 first
160 let fdcontents fd =
161 let l = 4096 in
162 let b = Buffer.create l in
163 let s = Bytes.create l in
164 let rec loop () =
165 let n = tempfailureretry (Unix.read fd s 0) l in
166 if n = 0
167 then Buffer.contents b
168 else (
169 Buffer.add_subbytes b s 0 n;
170 loop ()
173 loop ()
175 let filecontents path =
176 let fd = Unix.openfile path [Unix.O_RDONLY] 0o0 in
177 match fdcontents fd with
178 | exception exn ->
179 error "failed to read contents of %s: %s" path @@ exntos exn
180 | s ->
181 Ne.clo fd @@ error "failed to close descriptor for %s: %s" path;
184 let getcmdoutput errfun cmd =
185 let reperror fmt = Printf.kprintf errfun fmt in
186 let clofail s e = error "failed to close %s: %s" s e in
187 match Unix.pipe () with
188 | exception exn ->
189 reperror "pipe failed: %s" @@ exntos exn;
191 | (r, w) ->
192 match spawn cmd [r, -1; w, 1] with
193 | exception exn ->
194 reperror "failed to execute %S: %s" cmd @@ exntos exn;
196 | pid ->
197 Ne.clo w @@ clofail "write end of the pipe";
198 let s =
199 match Unix.waitpid [] pid with
200 | exception exn ->
201 reperror "waitpid on %S %d failed: %s" cmd pid @@ exntos exn;
203 | _pid, Unix.WEXITED 0 ->
204 begin
205 match fdcontents r with
206 | exception exn ->
207 reperror "failed to read output of %S: %s" cmd @@ exntos exn;
209 | s ->
210 let l = String.length s in
211 if l > 0 && s.[l-1] = '\n'
212 then String.sub s 0 (l-1)
213 else s
214 end;
215 | _pid, Unix.WEXITED n ->
216 reperror "%S exited with error code %d" cmd n;
218 | _pid, Unix.WSIGNALED n ->
219 reperror "%S was killed with signal %d" cmd n;
221 | _pid, Unix.WSTOPPED n ->
222 reperror "%S was stopped by signal %d" cmd n;
225 Ne.clo r @@ clofail "read end of the pipe";
228 let geturl =
229 let re = Str.regexp {|.*\(\(https?\|ftp\|mailto\|file\)://[^ ]+\).*|} in
230 fun s -> if Str.string_match re s 0
231 then Str.matched_group 1 s
232 else E.s
234 let substratis s pos subs =
235 let subslen = String.length subs in
236 if String.length s - pos >= subslen
237 then
238 let rec cmp i = i = subslen || (s.[pos+i] = subs.[i]) && cmp (i+1)
239 in cmp 0
240 else false
242 let w8 = Bytes.set_uint8
243 let r8 = Bytes.get_uint8
244 let w16 = Bytes.set_uint16_le
245 let r16 = Bytes.get_uint16_le
246 let r16s = Bytes.get_int16_le
247 let w32 s pos i = w16 s pos i; w16 s (pos+2) (i lsr 16)
248 let r32 s pos = ((r16 s (pos+2)) lsl 16) lor (r16 s pos)
249 let r32s s pos = Bytes.get_int32_le s pos |> Int32.to_int
251 let vlogf = ref ignore
252 let vlog fmt = Printf.kprintf !vlogf fmt
254 let pipef ?(closew=true) cap f cmd =
255 match Unix.pipe () with
256 | exception exn -> dolog "%s cannot create pipe: %S" cap @@ exntos exn
257 | (r, w) ->
258 begin match spawn cmd [r, 0; w, -1] with
259 | exception exn -> dolog "%s: cannot execute %S: %s" cap cmd @@ exntos exn
260 | _pid -> f w
261 end;
262 Ne.clo r (dolog "%s failed to close r: %s" cap);
263 if closew then Ne.clo w (dolog "%s failed to close w: %s" cap)
265 let selstring selcmd s =
266 pipef "selstring" (fun w ->
268 let l = String.length s in
269 let bytes = Bytes.unsafe_of_string s in
270 let n = tempfailureretry (Unix.write w bytes 0) l in
271 if n != l
272 then dolog "failed to write %d characters to sel pipe, wrote %d" l n;
273 with exn -> dolog "failed to write to sel pipe: %s" @@ exntos exn
274 ) selcmd