open String let string_after s n = String.sub s n (String.length s - n) let quote s = let len = String.length s in let buf = Buffer.create (2 * len) in for i = 0 to len - 1 do match s.[i] with '[' | ']' | '*' | '.' | '\\' | '?' | '+' | '^' | '$' as c -> Buffer.add_char buf '\\'; Buffer.add_char buf c | c -> Buffer.add_char buf c done; Buffer.contents buf (* Not tail recursive for "performance", please choose low values for [max]. The idea is that max is always small because it's hard code *) let split_char_bounded str ~on ~max = if str = "" then [] else if max = 1 then [str] else let rec loop offset tokens = if tokens = max - 1 then [sub str offset (length str - offset)] else try let index = index_from str offset on in if index = offset then ""::(loop (offset + 1) (tokens + 1)) else let token = String.sub str offset (index - offset) in token::(loop (index + 1) (tokens + 1)) with Not_found -> [sub str offset (length str - offset)] in loop 0 0 let split_char_unbounded str ~on = if str = "" then [] else let rec loop acc offset = try begin let index = rindex_from str offset on in if index = offset then loop (""::acc) (index - 1) else let token = sub str (index + 1) (offset - index) in loop (token::acc) (index - 1) end with Not_found -> (sub str 0 (offset + 1))::acc in loop [] (length str - 1) let of_char = String.make 1 let full_split str ~on = if str = "" then [] else let sep = of_char on in let rec loop acc offset = try begin let index = rindex_from str offset on in if index = offset then loop (sep::acc) (index - 1) else let token = sub str (index + 1) (offset - index) in loop (sep::token::acc) (index - 1) end with Not_found -> if offset >= 0 then (sub str 0 (offset + 1))::acc else acc in loop [] (length str - 1) (* copying core's convention for String.split but with an optional max argument *) let split ?max s ~on = match max with | None -> split_char_unbounded s ~on | Some max -> (* assert (max < 100); *) split_char_bounded s ~on ~max let rindex_from_on s ~offset ~on = let rec loop i = if i < 0 then raise Not_found else if String.contains on s.[i] then i else loop (i - 1) in loop offset let trim_left_sub s ~pos ~len ~chars = let start_pos = let final = pos + len in let rec loop last_char i = if i = final then last_char else if String.contains chars s.[i] then loop (i + 1) (i + 1) else last_char in loop pos pos in let new_len = len - (start_pos - pos) in String.sub s start_pos new_len let split_trim_left str ~on ~trim = if str = "" then [] else let rec loop acc offset = try begin let index = rindex_from_on str ~offset ~on in if index = offset then loop (""::acc) (index - 1) else let token = trim_left_sub str ~pos:(index + 1) ~len:(offset - index) ~chars:trim in loop (token::acc) (index - 1) end with Not_found -> (trim_left_sub str ~pos:0 ~len:(offset + 1) ~chars:trim)::acc in loop [] (length str - 1) exception Found_int of int let first_char_ne s c = String.length s > 0 && s.[0] <> c let trim_left s = if first_char_ne s ' ' then s else let len = String.length s in try for i=0 to len - 1 do if s.[i] <> ' ' then raise (Found_int i) done; "" with Found_int non_space -> sub s non_space (len - non_space) let substr_eq ?(start=0) s ~pattern = try for i = 0 to String.length pattern - 1 do if s.[i + start] <> pattern.[i] then raise Exit done; true with _ -> false let find_from ?(start=0) str ~pattern = try for i = start to (String.length str) - (String.length pattern) do if substr_eq ~start:i str ~pattern then raise (Found_int i) done; None with | Found_int i -> Some i | _ -> None let find_min l ~f = let rec loop x fx = function | [] -> Some (x, fx) | x'::xs -> let fx' = f x' in if fx' < fx then loop x' fx' xs else loop x fx xs in match l with | [] -> None | x::xs -> loop x (f x) xs let replace_all str ~pattern ~with_ = let (slen, plen) = String.(length str, length pattern) in let buf = Buffer.create slen in let rec loop i = match find_from ~start:i str ~pattern with | None -> Buffer.add_substring buf str i (slen - i); Buffer.contents buf | Some j -> Buffer.add_substring buf str i (j - i); Buffer.add_string buf with_; loop (j + plen) in loop 0 exception Found_replace of int * string * string let replace_all_assoc str tbl = let slen = String.length str in let buf = Buffer.create slen in let rec loop i = if i >= slen then Buffer.contents buf else let r = try let found = ref false in let e = find_min tbl ~f:(fun (pattern, with_) -> match find_from ~start:i str ~pattern with | None -> max_int | Some j when j = i -> raise (Found_replace (j, pattern, with_)) | Some j -> found := true; j) in match e with | None -> None | Some ((pattern, with_), j) when !found -> Some (j, pattern, with_) | Some _ -> None with Found_replace (j, pattern, with_) -> Some (j, pattern, with_) in match r with | None -> Buffer.add_substring buf str i (slen - i); Buffer.contents buf | Some (j, pattern, with_) -> Buffer.add_substring buf str i (j - i); Buffer.add_string buf with_; loop (j + String.length pattern) in loop 0 let iteri f l = let rec loop i = function | [] -> () | x::xs -> (f i x); loop (succ i) xs in loop 0 l let of_list xs = let l = List.length xs in let s = Bytes.create l in iteri (fun i c -> Bytes.set s i c) xs; Bytes.unsafe_to_string s let to_list s = let rec loop acc i = if i = -1 then acc else loop (s.[i] :: acc) (pred i) in loop [] (String.length s - 1) let of_array a = let len = Array.length a in let bytes = Bytes.create len in for i = 0 to len - 1 do Bytes.set bytes i a.(i) done; Bytes.unsafe_to_string bytes let to_array s = Array.init (String.length s) (String.get s) (* ripped off from one of dbuenzli's libs *) let cut s ~on = let sep_max = length on - 1 in if sep_max < 0 then invalid_arg "Stringext.cut: empty separator" else let s_max = length s - 1 in if s_max < 0 then None else let k = ref 0 in let i = ref 0 in (* We run from the start of [s] to end with [i] trying to match the first character of [on] in [s]. If this matches, we verify that the whole [on] is matched using [k]. If it doesn't match we continue to look for [on] with [i]. If it matches we exit the loop and extract a substring from the start of [s] to the position before the [on] we found and another from the position after the [on] we found to end of string. If [i] is such that no separator can be found we exit the loop and return the no match case. *) try while (!i + sep_max <= s_max) do (* Check remaining [on] chars match, access to unsafe s (!i + !k) is guaranteed by loop invariant. *) if unsafe_get s !i <> unsafe_get on 0 then incr i else begin k := 1; while (!k <= sep_max && unsafe_get s (!i + !k) = unsafe_get on !k) do incr k done; if !k <= sep_max then (* no match *) incr i else raise Exit end done; None (* no match in the whole string. *) with | Exit -> (* i is at the beginning of the separator *) let left_end = !i - 1 in let right_start = !i + sep_max + 1 in Some (sub s 0 (left_end + 1), sub s right_start (s_max - right_start + 1)) let rcut s ~on = let sep_max = length on - 1 in if sep_max < 0 then invalid_arg "Stringext.rcut: empty separator" else let s_max = length s - 1 in if s_max < 0 then None else let k = ref 0 in let i = ref s_max in (* We run from the end of [s] to the beginning with [i] trying to match the last character of [on] in [s]. If this matches, we verify that the whole [on] is matched using [k] (we do that backwards). If it doesn't match we continue to look for [on] with [i]. If it matches we exit the loop and extract a substring from the start of [s] to the position before the [on] we found and another from the position after the [on] we found to end of string. If [i] is such that no separator can be found we exit the loop and return the no match case. *) try while (!i >= sep_max) do if unsafe_get s !i <> unsafe_get on sep_max then decr i else begin (* Check remaining [on] chars match, access to unsafe_get s (sep_start + !k) is guaranteed by loop invariant. *) let sep_start = !i - sep_max in k := sep_max - 1; while (!k >= 0 && unsafe_get s (sep_start + !k) = unsafe_get on !k) do decr k done; if !k >= 0 then (* no match *) decr i else raise Exit end done; None (* no match in the whole string. *) with | Exit -> (* i is at the end of the separator *) let left_end = !i - sep_max - 1 in let right_start = !i + 1 in Some (sub s 0 (left_end + 1), sub s right_start (s_max - right_start + 1)) let chop_prefix s ~prefix = let prefix_l = String.length prefix in let string_l = String.length s in if prefix_l > string_l then None else try for i = 0 to prefix_l - 1 do if s.[i] <> prefix.[i] then raise Exit; done; Some (String.sub s prefix_l (string_l - prefix_l)) with _ -> None let drop s n = let l = String.length s in if n >= l then "" else String.sub s n (l - n) let take s n = if n >= String.length s then s else String.sub s 0 n