ocaml-containers/src/core/CCIO.ml
2024-05-13 20:57:53 -04:00

387 lines
8.6 KiB
OCaml

(* 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