utils: Add unique () function.
[goals.git] / src / utils.ml
1 (* Goalfile utilities
2  * Copyright (C) 2019 Richard W.M. Jones
3  * Copyright (C) 2019 Red Hat Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  *)
19
20 open Printf
21
22 let failwithf fs = ksprintf failwith fs
23
24 let (//) = Filename.concat
25 let is_directory d = try Sys.is_directory d with Sys_error _ -> false
26
27 let unique = let i = ref 0 in fun () -> incr i; !i
28
29 (* From OCaml 4.08 sources.  We can remove this when we can
30  * depend on min OCaml 4.08.
31  *)
32 let filter_map f =
33   let rec aux accu = function
34     | [] -> List.rev accu
35     | x :: l ->
36         match f x with
37         | None -> aux accu l
38         | Some v -> aux (v :: accu) l
39   in
40   aux []
41
42
43 (* From libguestfs sources. *)
44 let rec string_find s sub =
45   let len = String.length s in
46   let sublen = String.length sub in
47   let rec loop i =
48     if i <= len-sublen then (
49       let rec loop2 j =
50         if j < sublen then (
51           if s.[i+j] = sub.[j] then loop2 (j+1)
52           else -1
53         ) else
54           i (* found *)
55       in
56       let r = loop2 0 in
57       if r = -1 then loop (i+1) else r
58     ) else
59       -1 (* not found *)
60   in
61   loop 0
62
63 let rec split sep str =
64   let len = String.length sep in
65   let seplen = String.length str in
66   let i = string_find str sep in
67   if i = -1 then str, ""
68   else (
69     String.sub str 0 i, String.sub str (i + len) (seplen - i - len)
70   )
71
72 and nsplit ?(max = 0) sep str =
73   if max < 0 then
74     invalid_arg "String.nsplit: max parameter should not be negative";
75
76   (* If we reached the limit, OR if the pattern does not match the string
77    * at all, return the rest of the string as a single element list.
78    *)
79   if max = 1 || string_find str sep = -1 then
80     [str]
81   else (
82     let s1, s2 = split sep str in
83     let max = if max = 0 then 0 else max - 1 in
84     s1 :: nsplit ~max sep s2
85   )
86
87 let isspace c =
88   c = ' '
89   (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
90
91 let triml ?(test = isspace) str =
92   let i = ref 0 in
93   let n = ref (String.length str) in
94   while !n > 0 && test str.[!i]; do
95     decr n;
96     incr i
97   done;
98   if !i = 0 then str
99   else String.sub str !i !n
100
101 let trimr ?(test = isspace) str =
102   let n = ref (String.length str) in
103   while !n > 0 && test str.[!n-1]; do
104     decr n
105   done;
106   if !n = String.length str then str
107   else String.sub str 0 !n
108
109 let trim ?(test = isspace) str =
110   trimr ~test (triml ~test str)
111
112 let absolute_path path =
113   if not (Filename.is_relative path) then path else Sys.getcwd () // path