module Filename = Filename_new exception Error of string * string * exn (* operation_name, file_name, exception *) let error ~opn ~fn ~e = raise (Error (opn, fn, e)) let with_file op opname cl func filename = let ch = try op filename with | e -> error ~opn: opname ~fn: filename ~e in try let r = func ch in (cl ch; r) with | e -> (cl ch; raise e) let with_process_in cmd func = with_file Unix.open_process_in "with_process_in" Unix.close_process_in func cmd let with_file_in_bin fn func = with_file open_in_bin "with_file_in_bin" close_in func fn let with_file_out_bin fn func = with_file open_out_bin "with_file_out_bin" close_out func fn let with_file_out_gen flags mode fn func = with_file (open_out_gen flags mode) "with_file_out_gen" close_out func fn let is_directory d = try (Sys.file_exists d) && (Sys.is_directory d) with | e -> error ~opn: "is_directory" ~fn: d ~e let is_file d = try (Sys.file_exists d) && (not (Sys.is_directory d)) with | e -> error ~opn: "is_file" ~fn: d ~e let remove_file filename = if is_file filename then (try Sys.remove filename with | e -> error ~opn: "remove_file" ~fn: filename ~e) else failwith (Printf.sprintf "Filew.remove_file: not a file: %S" filename) let with_temp_file_opened_bin ~cleanup ~temp_dir prefix suffix func = let (path_tmp, out_ch) = try Filename.open_temp_file ~mode: [ Open_binary ] ~temp_dir prefix suffix with | e -> error ~opn: "with_temp_file_opened_bin/Filename.open_temp_file" ~fn: (Printf.sprintf "temp_dir=%S prefix=%S suffix=%S" temp_dir prefix suffix) ~e in let finally () = ((try close_out out_ch with | e -> error ~opn: "with_temp_file_opened_bin/close_out" ~fn: path_tmp ~e); if cleanup then remove_file path_tmp else ()) in try let r = func path_tmp out_ch in (finally (); r) with | e -> (finally (); raise e) let input_line_opt ch = try Some (input_line ch) with | End_of_file -> None let fold_channel_lines func init ch = let rec fold_channel_lines_inner init = match input_line_opt ch with | None -> init | Some line -> fold_channel_lines_inner (func init line) in fold_channel_lines_inner init let fold_file_lines func init filename = with_file_in_bin filename (fold_channel_lines func init) let map_file_lines filename mapfunc = List.rev (fold_file_lines (fun rev_acc line -> (mapfunc line) :: rev_acc) [] filename) let iter_file_lines filename func = fold_file_lines (fun () line -> func line) () filename let channel_lines ch = List.rev (fold_channel_lines (fun rev_acc line -> line :: rev_acc) [] ch) let file_lines fn = with_file_in_bin fn channel_lines let slurp_ch ch = let strsz = 8192 in let buf = Buffer.create 1024 and str = String.make strsz '\x00' in let rec inner cursz = let insz = input ch str 0 strsz in if insz = 0 then Buffer.contents buf else (let newsz = cursz + insz in if newsz > Sys.max_string_length then failwith ("Filew.slurp_ch: file is bigger than " ^ ((string_of_int Sys.max_string_length) ^ " bytes")) else (Buffer.add_substring buf str 0 insz; inner newsz)) in inner 0 let slurp_bin filename = with_file_in_bin filename slurp_ch (* maybe replace with fixed buffer and with linking Unix for stat(). *) let copy_channels ?(bufsz = 4096) inch outch = if (bufsz < 0) || (bufsz > Sys.max_string_length) then invalid_arg "Filew.copy_channels: bufsz" else (let buf = String.make bufsz '\x00' in let rec inner () = let have_read = input inch buf 0 bufsz in if have_read = 0 then () else (output outch buf 0 have_read; inner ()) in inner ()) let copy_file src dst = try with_file_in_bin src (fun inch -> with_file_out_bin dst (fun outch -> copy_channels inch outch)) with | e -> failwith (Printf.sprintf "Filew.copy_file: can't copy \"%s\" to \"%s\": %s" src dst (Printexc.to_string e)) let try_create_dir dirname perm = try (Unix.mkdir dirname perm; assert (is_directory dirname); true) with | Unix.Unix_error _ -> false let temp_create_tries = 1000 let prng = Random.State.make_self_init () let create_temp_dir () = let tmp_root = Filename.temp_dir_name in let rec inner tries_left = if tries_left <= 0 then failwith "Filew.create_temp_dir: can't create temporary directory" else (let dir = Printf.sprintf "filew%06i" (Random.State.int prng 1000000) in let path = Filename.concat tmp_root dir in if try_create_dir path 0o700 then path else inner (tries_left - 1)) in inner temp_create_tries (* dumb name, but shell-style: -f, -d, -e *) let is_exists d = Sys.file_exists d let chdir dir = try Sys.chdir dir with | e -> failwith (Printf.sprintf "Filew.chdir: can't chdir to %S: %s" dir (Printexc.to_string e)) let with_cur_dir dir func = let old_dir = Sys.getcwd () in let finally () = chdir old_dir in try if not (is_directory dir) then failwith (Printf.sprintf "Filew.with_cur_dir: directory does not exist: %S" dir) else (let () = chdir dir in let r = func () in let () = finally () in r) with | e -> (finally (); raise e) let forA arr func = Array.iter func arr let readdir dir = try Sys.readdir dir with | e -> error ~opn: "readdir" ~fn: dir ~e let rec remove_directory_contents_rec dir = let entries = readdir dir in forA entries (fun entry -> let path = Filename.concat dir entry in if is_directory path then (remove_directory_contents_rec path; Unix.rmdir path) else Unix.unlink path) let remove_directory ~recursive dir = let fail msg = failwith ("Filew.remove_directory: " ^ msg) in if not (is_directory dir) then fail "not found or not a directory" else (if recursive then remove_directory_contents_rec dir else (); Unix.rmdir dir) let with_temp_dir func = let dir = create_temp_dir () in let finally () = remove_directory ~recursive: true dir in try let r = func dir in (finally (); r) with | e -> (finally (); raise e) let filename_NUL = if Sys.os_type = "Win32" then "NUL" else "/dev/null" exception Exists let file_line_exists filename pred = try (iter_file_lines filename (fun line -> if pred line then raise Exists else ()); false) with | Exists -> true let file_line_forall filename pred = not (file_line_exists filename (fun line -> not (pred line))) let rename src dst = try Sys.rename src dst with | e -> error ~opn: "rename" ~fn: (Printf.sprintf "src=%S dst=%S" src dst) ~e let rename_to_tmp path_orig prefix suffix = let dir_orig = Filename.dirname path_orig in let rec inner left = if left <= 0 then failwith (Printf.sprintf "Filew.rename_to_tmp: can't rename file %S to temporary name \ (directory %S, prefix %S, suffix %S)" path_orig dir_orig prefix suffix) else (let fn_tmp = Printf.sprintf "%s%06i%s" prefix (Random.State.int prng 1000000) suffix in let path_tmp = Filename.concat dir_orig fn_tmp in if is_exists path_tmp then inner (left - 1) else (rename path_orig path_tmp; path_tmp)) in inner temp_create_tries let rename_opt src dst = try (Sys.rename src dst; None) with | e -> Some e let replace_file ?(justcreatenewfile = false) path_orig func = let dir_orig = Filename.dirname path_orig and fn_orig = Filename.basename path_orig in let (processing_res, path_tmp) = (* подумать, как ловить ошибку в with -- ибо тут явно она, Sys_error при вызове replace_file. 0. ловить ли исключения в with-обёртке в try open_.. и try close_.. так, чтобы сообщать о них особым образом, типа failwith "Filew.with_..: open_in_bin failed"? 1. использовать ли Res в Filew? 2. придумать ли шнягу для оборачивания ошибок, возникающих только в with-обёртке? (видимо заменой пользовательской функции на что-то, следящее за исключениями? "with1_catch thehandler with_file_in_bin path_orig & fun ..." ) *) with_file_in_bin path_orig (fun in_ch -> with_temp_file_opened_bin ~cleanup: false ~temp_dir: dir_orig (fn_orig ^ ".") ".new" (fun path_tmp out_ch -> let res = try match func in_ch out_ch with | None -> `Ok | Some e -> `Error e with | e -> `Exn e in (res, path_tmp))) in match processing_res with | `Exn e -> (remove_file path_tmp; raise e) | `Error e -> (remove_file path_tmp; Some e) | `Ok -> (if justcreatenewfile then () else (let path_bak = rename_to_tmp path_orig (fn_orig ^ ".") ".bak" in let new_to_orig = rename_opt path_tmp path_orig in match new_to_orig with | None -> remove_file path_bak | Some e -> (rename path_bak path_orig; raise e)); None) let copy_files srcs dst_dir = if not (is_directory dst_dir) then failwith (Printf.sprintf "Filew.copy_files: not a directory: %S" dst_dir) else (let copied_files = ref [] in try List.iter (fun path -> let new_path = Filename.concat dst_dir (Filename.basename path) in let () = copy_file path new_path in copied_files := new_path :: !copied_files) srcs with | e -> (List.iter remove_file !copied_files; raise e)) let stream_of_channel_lines_gen ~close inch = Stream.from (let inch_ref = ref (Some inch) in fun _ -> match !inch_ref with | None -> None | Some inch -> (match input_line_opt inch with | (Some _ as some_line) -> some_line | None -> (if close then (close_in inch; inch_ref := None) else (); None))) let stream_of_channel_lines inch = stream_of_channel_lines_gen ~close: false inch let stream_of_file_lines path = let inch = try open_in_bin path with | e -> error ~opn: "stream_of_file_lines" ~fn: path ~e in stream_of_channel_lines_gen ~close: true inch