let assoc_opt key items = let rec search = function | [] -> None | (candidate, value) :: rest -> if candidate = key then Some value else search rest in search items let hashtbl_find_opt table key = try Some (Hashtbl.find table key) with Not_found -> None let rec filter_map f = function | [] -> [] | item :: rest -> ( match f item with | Some value -> value :: filter_map f rest | None -> filter_map f rest) let rec concat_map f = function | [] -> [] | item :: rest -> f item @ concat_map f rest let rec list_init count f = if count <= 0 then [] else let head = f 0 in let tail = list_init (count - 1) (fun index -> f (index + 1)) in head :: tail let rec take count items = if count <= 0 then [] else match items with | [] -> [] | item :: rest -> item :: take (count - 1) rest let rec drop count items = if count <= 0 then items else match items with [] -> [] | _ :: rest -> drop (count - 1) rest let contains value items = List.exists (fun item -> item = value) items let index_of_char text character = let rec search index = if index >= String.length text then None else if text.[index] = character then Some index else search (index + 1) in search 0 let string_before_char text character = match index_of_char text character with | Some index -> String.sub text 0 index | None -> text let starts_with prefix text = String.length text >= String.length prefix && String.sub text 0 (String.length prefix) = prefix let rec join separator = function | [] -> "" | [ single ] -> single | item :: rest -> item ^ separator ^ join separator rest