123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353(* This file is free software, part of containers. See file "license" for more details. *)(** {1 IO Utils} *)type'aor_error=('a,string)resulttype'agen=unit->'aoptionletgen_empty()=Noneletgen_singletonx=letdone_=reffalseinfun()->if!done_thenNoneelse(done_:=true;Somex)letgen_filter_mapfgen=(* tailrec *)letrecnext()=matchgen()with|None->None|Somex->matchfxwith|None->next()|(Some_)asres->resinnextletgen_of_arrayarr=letr=ref0infun()->if!r=Array.lengtharrthenNoneelse(letx=arr.(!r)inincrr;Somex)letgen_flat_mapfnext_elem=letstate=ref`Initinletrecnext()=match!statewith|`Init->get_next_gen()|`Rungen->beginmatchgen()with|None->get_next_gen()|(Some_)asx->xend|`Stop->Noneandget_next_gen()=matchnext_elem()with|None->state:=`Stop;None|Somex->trystate:=`Run(fx);next()withe->state:=`Stop;raiseeinnextletfinally_fx~h=tryletres=fxinhx;reswithe->hx;raiseeletwith_in?(mode=0o644)?(flags=[Open_text])filenamef=letic=open_in_gen(Open_rdonly::flags)modefilenameinfinally_fic~h:close_inletread_chunks_gen?(size=1024)ic=letbuf=Bytes.createsizeinletnext()=letn=inputicbuf0sizeinifn=0thenNoneelseSome(Bytes.sub_stringbuf0n)innextletread_chunks=read_chunks_genletread_lineic=trySome(input_lineic)withEnd_of_file->Noneletread_lines_genic=letstop=reffalseinfun()->if!stopthenNoneelsetrySome(input_lineic)withEnd_of_file->(stop:=true;None)letread_lines=read_lines_genletread_lines_lic=letl=ref[]intrywhiletruedol:=input_lineic::!ldone;assertfalsewithEnd_of_file->List.rev!l(* thanks to nicoo for this trick *)type_ret_type=|Ret_string:stringret_type|Ret_bytes:Bytes.tret_typeletread_all_:typea.op:aret_type->size:int->in_channel->a=fun~op~sizeic->letbuf=ref(Bytes.createsize)inletlen=ref0intrywhiletruedo(* resize *)if!len=Bytes.length!bufthen(buf:=Bytes.extend!buf0!len;);assert(Bytes.length!buf>!len);letn=inputic!buf!len(Bytes.length!buf-!len)inlen:=!len+n;ifn=0thenraiseExit;(* exhausted *)done;assertfalse(* never reached*)withExit->matchopwith|Ret_string->Bytes.sub_string!buf0!len|Ret_bytes->Bytes.sub!buf0!lenletread_all_bytes?(size=1024)ic=read_all_~op:Ret_bytes~sizeicletread_all?(size=1024)ic=read_all_~op:Ret_string~sizeic(*$R
let s = String.make 200 'y' in
let s = Printf.sprintf "a\nb\n %s\nlast line\n" s in
OUnit.bracket_tmpfile ~prefix:"test_containers" ~mode:[Open_creat; Open_trunc]
(fun (name, oc) ->
output_string oc s;
flush oc;
let s' = with_in name read_all in
OUnit.assert_equal ~printer:(fun s->s) s s'
) ()
*)letwith_out?(mode=0o644)?(flags=[Open_creat;Open_trunc;Open_text])filenamef=letoc=open_out_gen(Open_wronly::flags)modefilenameinfinally_foc~h:close_outletwith_out_a?mode?(flags=[])filenamef=with_out?mode~flags:(Open_wronly::Open_creat::Open_append::flags)filenamefletwrite_lineocs=output_stringocs;output_charoc'\n'letwrite_gen?(sep="")ocg=letrecrecurse()=matchg()with|None->()|Somes->output_stringocsep;output_stringocs;recurse()inmatchg()with|None->()|Somes->output_stringocs;recurse()letrecwrite_linesocg=matchg()with|None->()|Somel->write_lineocl;write_linesocgletwrite_lines_locl=List.iter(write_lineoc)l(* test {read,write}_lines. Need to concatenate the lists because some
strings in the random input might contain '\n' themselves *)(*$QR
Q.(list_of_size Gen.(0 -- 40) printable_string) (fun l ->
let l' = ref [] in
OUnit.bracket_tmpfile ~prefix:"test_containers" ~mode:[Open_creat; Open_trunc]
(fun (name, oc) ->
write_lines_l oc l;
flush oc;
l' := with_in name read_lines_l;
) ();
String.concat "\n" l = String.concat "\n" !l'
)
*)(*$QR
Q.(list_of_size Gen.(0 -- 40) printable_string) (fun l ->
let l' = ref [] in
OUnit.bracket_tmpfile ~prefix:"test_containers" ~mode:[Open_creat; Open_trunc]
(fun (name, oc) ->
write_lines oc (Gen.of_list l);
flush oc;
l' := with_in name (fun ic -> read_lines ic |> Gen.to_list);
) ();
String.concat "\n" l = String.concat "\n" !l'
)
*)letwith_in_out?(mode=0o644)?(flags=[Open_creat])filenamef=letic=open_in_gen(Open_rdonly::flags)modefilenameinletoc=open_out_gen(Open_wronly::flags)modefilenameintryletx=ficocinclose_outoc;(* must be first?! *)close_inic;xwithe->close_out_noerroc;close_in_noerric;raiseeletteefunsg()=matchg()with|None->None|Somexasres->List.iter(funf->tryfxwith_->())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
*)moduleFile=structtypet=stringletto_stringf=fletmakef=ifFilename.is_relativefthenFilename.concat(Sys.getcwd())felsefletexistsf=Sys.file_existsfletis_directoryf=Sys.is_directoryfletremove_exnf=Sys.removefletremovef=tryOk(Sys.removef)withexn->Error(Printexc.to_stringexn)letread_exnf=with_inf(read_all_~op:Ret_string~size:4096)letreadf=tryOk(read_exnf)withe->Error(Printexc.to_stringe)letappend_exnfx=with_out~flags:[Open_append;Open_creat;Open_text]f(funoc->output_stringocx;flushoc)letappendfx=tryOk(append_exnfx)withe->Error(Printexc.to_stringe)letwrite_exnfx=with_outf(funoc->output_stringocx;flushoc)letwritefx=tryOk(write_exnfx)withe->Error(Printexc.to_stringe)letremove_noerrf=trySys.removefwith_->()letread_dir_based=ifSys.is_directorydthenletarr=Sys.readdirdingen_of_arrayarrelsefun()->Noneletcons_xtl=letfirst=reftrueinfun()->if!firstthen(first:=false;Somex)elsetl()letrecwalkd=ifnot(Sys.file_existsd)thengen_emptyelseifSys.is_directorydthen((* try to list the directory *)letarr=trySys.readdirdwithSys_error_->[||]inlettail=gen_of_arrayarrinlettail=gen_flat_map(funs->walk(Filename.concatds))tailincons_(`Dir,d)tail)elsegen_singleton(`File,d)(*$R
OUnit.assert_bool "walk categorizes files"
(File.walk "."
|> Gen.for_all
(function
| `File, f -> not (Sys.is_directory f)
| `Dir, f -> Sys.is_directory f
)
)
*)letwalk_ld=letl=ref[]inletg=walkdinletrecaux()=matchg()with|None->!l|Somex->l:=x::!l;aux()inaux()typewalk_item=[`File|`Dir]*tletread_dir?(recurse=false)d=ifrecursethengen_filter_map(function|`File,f->Somef|`Dir,_->None)(walkd)elseread_dir_basedletshow_walk_item((i,f):walk_item)=(matchiwith|`File->"file:"|`Dir->"dir:")^fletwith_temp?temp_dir~prefix~suffixf=letname=Filename.temp_file?temp_dirprefixsuffixinfinally_fname~h:remove_noerrend