source: code/trunk/lib/file_store.ml@ 11

Last change on this file since 11 was 11, checked in by fox, 3 years ago

Remove redundant Store.with_id

File size: 5.2 KB
Line 
1type t = string
2type item_t = t list
3type record_t = Text.t * item_t
4
5let extension = ".txt"
6let def_dir () =
7 let share = Sys.getenv "HOME" ^ "/.local/share/texts/" in
8 try Sys.getenv "txtdir" with Not_found ->
9 match Sys.is_directory share with
10 | true -> share
11 | false | exception (Sys_error _) -> "."
12
13let to_string f =
14 let ic = open_in f in
15 let s = really_input_string ic (in_channel_length ic) in
16 close_in ic;
17 s
18
19let fold_file_line fn init file = match open_in file with
20 | exception (Sys_error msg) -> prerr_endline msg; init
21 | file ->
22 let rec read acc = match input_line file with
23 | "" as s | s when String.get s 0 = '#' -> read acc
24 | s -> read (fn s acc)
25 | exception End_of_file -> close_in file; acc
26 in read init
27
28let file path str = let o = open_out path in output_string o str; close_out o
29
30let to_text path =
31 if Filename.extension path = extension then
32 (to_string path |> Text.of_string |> Result.map_error (fun m -> path ^": "^ m))
33 else Error (Printf.sprintf "Not txt: %s" path)
34
35let newest (a,_pa) (b,_pb) = Text.newest a b
36let oldest (a,_pa) (b,_pb) = Text.oldest a b
37
38let list_iter fn dir paths =
39 let link f = match to_text (Filename.concat dir f) with
40 | Ok t -> fn dir t f | Error s -> prerr_endline s in
41 List.iter link paths
42
43module TextMap = Map.Make(Text)
44
45type iteration_t = item_t TextMap.t
46let new_iteration = TextMap.empty
47
48(*let iter_valid_text pred fn path =*)
49(* match to_text path with Error _ -> () | Ok t -> if pred t then fn (t, p)*)
50
51let fold_valid_text pred it path =
52 match to_text path with Error _ -> it
53 | Ok t -> if pred t then (TextMap.update t
54 (function None -> Some [path] | Some ps -> Some (path::ps)) it
55 ) else it
56
57let split_filetypes files =
58 let acc (dirs, files) x = if Sys.is_directory x
59 then (x::dirs, files) else (dirs, x::files) in
60 List.fold_left acc ([],[]) files
61
62(* Compare file system nodes to skip reparsing? *)
63let list_fs ?(r=false) dir =
64 let valid_dir f = r && String.get f 0 <> '.' && Sys.is_directory f in
65 let expand_dir d = Array.(to_list @@ map (Filename.concat d) (Sys.readdir d)) in
66 let rec loop result = function
67 | f::fs when valid_dir f -> expand_dir f |> List.append fs |> loop result
68 | f::fs -> loop (f::result) fs
69 | [] -> result in
70 let dirs = if dir = "." then Array.to_list (Sys.readdir dir) else
71 if not r then expand_dir dir else [dir] in
72 loop [] dirs
73
74let list_take n =
75 let rec take acc n = function [] -> []
76 | x::_ when n = 1 -> x::acc
77 | x::xs -> take (x::acc) (n-1) xs
78 in take [] n
79
80let fold_sort_take ?(predicate=fun _ -> true) ?(number=None) comp flist =
81 (match number with None -> (fun x -> x) | Some n -> list_take n)
82 @@ List.fast_sort comp @@ TextMap.bindings
83 @@ List.fold_left (fold_valid_text predicate) new_iteration flist
84
85let iter ?(r=false) ?(dir=def_dir ()) ?(predicate=fun _ -> true) ?order ?number fn =
86 let flist = list_fs ~r dir in match order with
87 | Some comp -> List.iter fn @@ fold_sort_take ~predicate ~number comp flist
88 | None -> List.iter fn @@ TextMap.bindings @@
89 List.fold_left (fold_valid_text predicate) new_iteration flist
90
91let fold ?(r=false) ?(dir=def_dir ()) ?(predicate=fun _ -> true) ?order ?number fn acc =
92 let flist = list_fs ~r dir in match order with
93 | Some comp -> List.fold_left fn acc @@ fold_sort_take ~predicate ~number comp flist
94 | None -> List.fold_left fn acc @@ TextMap.bindings @@
95 List.fold_left (fold_valid_text predicate) new_iteration flist
96
97let with_dir ?(descr="") ?(perm=0o740) dir =
98 let mkdir dir = match Unix.mkdir dir perm with
99 | exception Unix.Unix_error (EEXIST, _, _) -> ()
100 | exception Unix.Unix_error (code, _fn, arg) ->
101 failwith @@ Printf.sprintf "Error %s making %s dir: %s"
102 (Unix.error_message code) descr arg
103 | _ -> () in
104 let rec mkeach path = function [] | [""] -> () | ""::t -> mkeach path t
105 | hd::t -> let d = Filename.concat path hd in mkdir d; mkeach d t in
106 mkeach
107 (if Filename.is_relative dir then "" else "/")
108 (String.split_on_char '/' dir)
109
110let rec with_dirs = function [] -> () | (d, descr)::tl -> with_dir ~descr d; with_dirs tl
111
112let versioned_basename_of_title ?(version=0) repo extension (title : string) =
113 let basename = Text.string_alias title in
114 let rec next version =
115 let candidate = Filename.concat repo
116 (basename ^ "." ^ string_of_int version ^ extension) in
117 if Sys.file_exists candidate then next (succ version) else candidate
118 in
119 next version
120
121let id_filename repo extension text =
122 let basename = Text.alias text in
123 let candidate = Filename.concat repo (text.id ^ "." ^ basename ^ extension) in
124 if Sys.file_exists candidate then Error "Name clash, try again" else Ok candidate
125
126let with_text ?(dir=def_dir ()) new_text =
127 match id_filename dir extension new_text with
128 | Error _ as e -> e
129 | Ok path ->
130 try file path (Text.to_string new_text); Ok (path, new_text)
131 with Sys_error s -> Error s
132
133module Config = struct
134 type t = string Store.KV.t
135 let key_value k v a = Store.KV.add k (String.trim v) a
136end
137
138let of_kv_file path =
139 let open Text_parse in
140 let subsyntaxes = Parsers.Key_value.[|
141 (module Make (Config) : Parser.S with type t = Config.t); (module Make (Config)); |] in
142 let of_string text acc =
143 Parser.parse subsyntaxes { text; pos = 0; right_boundary = String.length text - 1 } acc in
144 of_string (to_string @@ path) Store.KV.empty
Note: See TracBrowser for help on using the repository browser.