(* This file is free software, part of containers. See file "license" for more details. *) (** {1 IO Utils} *) type 'a iter = ('a -> unit) -> unit type 'a or_error = ('a, string) result type 'a gen = unit -> 'a option let gen_empty () = None let gen_singleton x = let done_ = ref false in fun () -> if !done_ then None else ( done_ := true; Some x ) let gen_filter_map f gen = (* tailrec *) let rec next () = match gen () with | None -> None | Some x -> (match f x with | None -> next () | Some _ as res -> res) in next let gen_of_array arr = let r = ref 0 in fun () -> if !r = Array.length arr then None else ( let x = arr.(!r) in incr r; Some x ) let rec gen_iter f g = match g () with | None -> () | Some x -> f x; gen_iter f g let gen_flat_map f next_elem = let state = ref `Init in let rec next () = match !state with | `Init -> get_next_gen () | `Run gen -> (match gen () with | None -> get_next_gen () | Some _ as x -> x) | `Stop -> None and get_next_gen () = match next_elem () with | None -> state := `Stop; None | Some x -> (try state := `Run (f x); next () with e -> state := `Stop; raise e) in next type 'a seq_of_gen_state_ = | Of_gen_thunk of 'a gen | Of_gen_saved of 'a Seq.node let seq_of_gen_ g = let rec consume r () = match !r with | Of_gen_saved cons -> cons | Of_gen_thunk g -> (match g () with | None -> r := Of_gen_saved Seq.Nil; Nil | Some x -> let tl = consume (ref (Of_gen_thunk g)) in let l = Seq.Cons (x, tl) in r := Of_gen_saved l; l) in consume (ref (Of_gen_thunk g)) let finally_ f x ~h = try let res = f x in h x; res with e -> h x; raise e let with_in ?(mode = 0o644) ?(flags = [ Open_text ]) filename f = let ic = open_in_gen (Open_rdonly :: flags) mode filename in finally_ f ic ~h:close_in let read_chunks_gen ?(size = 1024) ic = let buf = Bytes.create size in let next () = let n = input ic buf 0 size in if n = 0 then None else Some (Bytes.sub_string buf 0 n) in next let read_chunks_iter ?size ic = let g = read_chunks_gen ?size ic in fun yield -> gen_iter yield g let read_chunks_seq ?size ic = seq_of_gen_ (read_chunks_gen ?size ic) let read_line ic = try Some (input_line ic) with End_of_file -> None let read_lines_gen ic = let stop = ref false in fun () -> if !stop then None else ( try Some (input_line ic) with End_of_file -> stop := true; None ) let read_lines_seq ic = seq_of_gen_ (read_lines_gen ic) let read_lines_iter ic = let g = read_lines_gen ic in fun yield -> gen_iter yield g let read_lines_l ic = let l = ref [] in try while true do l := input_line ic :: !l done; assert false with End_of_file -> List.rev !l (* thanks to nicoo for this trick *) type _ ret_type = | Ret_string : string ret_type | Ret_bytes : Bytes.t ret_type let read_all_ : type a. op:a ret_type -> size:int -> in_channel -> a = fun ~op ~size ic -> let buf = ref (Bytes.create size) in let len = ref 0 in try while true do (* resize *) if !len = Bytes.length !buf then buf := Bytes.extend !buf 0 !len; assert (Bytes.length !buf > !len); let n = input ic !buf !len (Bytes.length !buf - !len) in len := !len + n; if n = 0 then raise Exit (* exhausted *) done; assert false (* never reached*) with Exit -> (match op with | Ret_string -> Bytes.sub_string !buf 0 !len | Ret_bytes -> Bytes.sub !buf 0 !len) let read_all_bytes ?(size = 1024) ic = read_all_ ~op:Ret_bytes ~size ic let read_all ?(size = 1024) ic = read_all_ ~op:Ret_string ~size ic let with_out ?(mode = 0o644) ?(flags = [ Open_creat; Open_trunc; Open_text ]) filename f = let oc = open_out_gen (Open_wronly :: flags) mode filename in finally_ f oc ~h:close_out let with_out_a ?mode ?(flags = []) filename f = with_out ?mode ~flags:(Open_wronly :: Open_creat :: Open_append :: flags) filename f let write_line oc s = output_string oc s; output_char oc '\n' let write_gen ?(sep = "") oc g = let rec recurse g = match g () with | None -> () | Some s -> output_string oc sep; output_string oc s; recurse g in match g () with | None -> () | Some s -> output_string oc s; recurse g let write_seq ?(sep = "") oc seq : unit = let rec recurse g = match g () with | Seq.Nil -> () | Seq.Cons (s, seq) -> output_string oc sep; output_string oc s; recurse seq in match seq () with | Seq.Nil -> () | Seq.Cons (s, seq) -> output_string oc s; recurse seq let rec write_lines oc g = match g () with | None -> () | Some l -> write_line oc l; write_lines oc g let write_lines_seq oc seq = Seq.iter (write_line oc) seq let write_lines_iter oc i = i (write_line oc) let write_lines_l oc l = List.iter (write_line oc) l (* test {read,write}_lines. Need to concatenate the lists because some strings in the random input might contain '\n' themselves *) let with_in_out ?(mode = 0o644) ?(flags = [ Open_creat ]) filename f = let ic = open_in_gen (Open_rdonly :: flags) mode filename in let oc = open_out_gen (Open_wronly :: flags) mode filename in try let x = f ic oc in close_out oc; (* must be first?! *) close_in ic; x with e -> close_out_noerr oc; close_in_noerr ic; raise e let copy_into ?(bufsize = 4_096) ic oc : unit = let buf = Bytes.create bufsize in let cont = ref true in while !cont do let n = input ic buf 0 bufsize in if n > 0 then output oc buf 0 n else cont := false done let tee funs g () = match g () with | None -> None | Some x as res -> List.iter (fun f -> f x) funs; res (* TODO: lines/unlines: string gen -> string gen *) (* TODO: words: string gen -> string gen, with a state machine that goes: - 0: read input chunk - switch to "search for ' '", and yield word - goto 0 if no ' ' found - yield leftover when g returns Stop *) module File = struct type t = string let to_string f = f let make f = if Filename.is_relative f then Filename.concat (Sys.getcwd ()) f else f let exists f = Sys.file_exists f let is_directory f = Sys.is_directory f let remove_exn f = Sys.remove f let remove f = try Ok (Sys.remove f) with exn -> Error (Printexc.to_string exn) let read_exn f = with_in f (read_all_ ~op:Ret_string ~size:4096) let read f = try Ok (read_exn f) with e -> Error (Printexc.to_string e) let append_exn f x = with_out ~flags:[ Open_append; Open_creat; Open_text ] f (fun oc -> output_string oc x; flush oc) let append f x = try Ok (append_exn f x) with e -> Error (Printexc.to_string e) let write_exn f x = with_out f (fun oc -> output_string oc x; flush oc) let write f x = try Ok (write_exn f x) with e -> Error (Printexc.to_string e) let remove_noerr f = try Sys.remove f with _ -> () let read_dir_base d = if Sys.is_directory d then ( let arr = Sys.readdir d in gen_of_array arr ) else fun () -> None let cons_ x tl = let first = ref true in fun () -> if !first then ( first := false; Some x ) else tl () let rec walk d = if not (Sys.file_exists d) then gen_empty else if Sys.is_directory d then ( (* try to list the directory *) let arr = try Sys.readdir d with Sys_error _ -> [||] in let tail = gen_of_array arr in let tail = gen_flat_map (fun s -> walk (Filename.concat d s)) tail in cons_ (`Dir, d) tail ) else gen_singleton (`File, d) let walk_seq d = seq_of_gen_ (walk d) let walk_iter d yield = gen_iter yield (walk d) let walk_l d = let l = ref [] in let g = walk d in let rec aux () = match g () with | None -> !l | Some x -> l := x :: !l; aux () in aux () type walk_item = [ `File | `Dir ] * t let read_dir ?(recurse = false) d = if recurse then gen_filter_map (function | `File, f -> Some f | `Dir, _ -> None) (walk d) else read_dir_base d let show_walk_item ((i, f) : walk_item) = (match i with | `File -> "file:" | `Dir -> "dir:") ^ f let with_temp ?temp_dir ~prefix ~suffix f = let name = Filename.temp_file ?temp_dir prefix suffix in finally_ f name ~h:remove_noerr end