package ocp-ocamlres

  1. Overview
  2. Docs

Source file oCamlRes.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
(* Main entry point of the OCamlRes library. *)

(* This file is part of ocp-ocamlres - main library
 * (C) 2013 OCamlPro - Benjamin CANOU
 *
 * ocp-ocamlres is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later
 * version, with linking exception.
 *
 * ocp-ocamlres is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the LICENSE file for more details *)

module Path = struct

  type t = dirs * name option
  and dirs = string list
  and name = string * ext option
  and ext = string

  let split_ext pstr =
    let len = String.length pstr in
    let rec loop cur last =
      if cur < 0 || pstr.[cur] = '/' then cut last
      else if pstr.[cur] = '.' then loop (cur - 1) (cur - 1)
      else loop (cur - 1) last
    and cut pos =
      if pos = len - 1 then
        (pstr, None)
      else
        (String.sub pstr 0 (pos + 1),
         Some (String.sub pstr (pos + 2) (len - pos - 2)))
    in
    loop (len - 1) (len - 1)

  let split_base pstr =
    let len = String.length pstr in
    let rec loop cur =
      if cur < 0 then ("", Some pstr)
      else if pstr.[cur] = '/' then
        if cur = len - 1 then (pstr, None)
        else (String.sub pstr 0 cur,
              Some (String.sub pstr (cur + 1) (len - cur - 1)))
      else loop (cur - 1)
    in
    loop (len - 1)

  let split_dirs pstr =
    let len = String.length pstr in
    let rec loop acc cur last =
      if cur < 0 || pstr.[cur] = '/' then cut acc cur last
      else loop acc (cur - 1) last
    and cut acc pos last =
      let acc =
        if pos = last then acc
        else String.sub pstr (pos + 1) (last - pos) :: acc
      in
      if pos < 0 then acc else loop acc (pos - 1) (pos - 1)
    in loop [] (len - 1) (len - 1)

  let shorten (dirs, file) =
    let rec loop acc dirs =
      match acc, dirs with
      | [], ".." :: tl -> loop [] tl
      | _ :: pacc, ".." :: tl -> loop pacc tl
      | _, "." :: tl -> loop acc tl
      | _, d :: tl -> loop (d :: acc) tl
      | _, [] -> List.rev acc
    in loop [] dirs, file

  let name_of_string pstr =
    if String.length pstr = 0 then invalid_arg "OCamlRes.Path.name_of_string" ;
    split_ext pstr

  let string_of_name (name, ext) =
    match ext with
    | None -> name
    | Some ext -> name ^ "." ^ ext

  let of_string pstr =
    if String.length pstr = 0 then invalid_arg "OCamlRes.Path.of_string" ;
    let dirs, base = split_base pstr in
    let path =
      match base with
      | None -> split_dirs dirs, None
      | Some ("." | "..") -> split_dirs pstr, None
      | Some base -> split_dirs dirs, (Some (split_ext base))
    in shorten path

  let to_string (dirs, file) =
    let open Buffer in
    let buf = create 255 in
    List.iter (fun p -> add_char buf '/' ; add_string buf p) dirs ;
    (match file with
     | None -> ()
     | Some (b, ext) -> add_char buf '/' ; add_string buf b ;
       match ext with
       | None -> ()
       | Some e -> add_char buf '.' ; add_string buf e) ;
    Buffer.contents buf
end

module Res = struct

  type 'a node =
    | Dir of string * 'a node list
    | File of string * 'a
    | Error of string

  type 'a root =
    'a node list
  module SM = Map.Make (String)
  module SS = Set.Make (String)

  let rec map_node f = function
    | Dir (n, l) -> Dir (n, map f l)
    | File (n, v) -> File (n, f v)
    | Error e -> Error e

  and map f l =
    List.map (map_node f) l

  let rec merge_nodes node1 node2 =
    match node1, node2 with
    | Dir (n1, l1), Dir (n2, l2) ->
      if n1 <> n2 then
        [ node1 ; node2 ]
      else
        [ Dir (n1, merge l1 l2) ]
    | (File (n, _) as f), (Dir (nd, _) as dir)
    | (Dir (nd, _) as dir), (File (n, _) as f) ->
      if n <> nd then
        [ f ; dir ]
      else
        [ Error ("unmergeable versions of " ^ n) ]
    | (File (n1, c1) as f1), (File (n2, c2) as f2) ->
      if n1 <> n2 || c1 = c2 then
        [ f1 ; f2 ]
      else
        [ Error ("unmergeable versions of " ^ n1) ]
    | (Error _ as e), n | n, (Error _ as e) ->
      [ e ; n ]

  and merge (rl : 'a root) (rr : 'a root) : 'a root =
    let files = ref SM.empty in
    let errors = ref SS.empty in
    let do_one =
      List.iter
        (fun node ->
           let to_add = match node with
             | Dir (n, _) | File (n, _) as f ->
               (try merge_nodes f (SM.find n !files) with Not_found -> [ f ])
             | Error _ as e -> [ e ]
           in
           List.iter
             (function
               | Error msg ->
                 errors := SS.add msg !errors
               | Dir (n, _) | File (n, _) as f ->
                 files := SM.add n f !files)
             to_add)
    in
    do_one rl ;
    do_one rr ;
    snd (List.split (SM.bindings !files))
    @ List.map (fun msg -> Error msg) (SS.elements !errors )

  let rec find (path : Path.t) (root : 'a root) : 'a =
    match root, path with
    | File (name, data) :: ns, ([d], None) -> (* let's be flexible *)
      if name = d then data else find path ns
    | File (name, data) :: ns, ([], Some n) ->
      if name = Path.string_of_name n then data else find path ns
    | Dir (name, ns) :: ps, (d :: ds, f) ->
      if name = d then find (ds, f) ns else find path ps
    | (Error _ | Dir _ | File _) :: ps, (_, Some n) ->
      find path ps
    | _, _ -> raise Not_found

  let rec find_dir (path : Path.t) (root : 'a root) : 'a root =
    match root, path with
    | _, ([], None) -> root
    | [], _ -> raise Not_found
    | Dir (name, ns) :: ps, (d :: ds, f) ->
      if name = d then find_dir (ds, f) ns else find_dir path ps
    | Dir (name, ns) :: ps, ([], Some f) -> (* let's be flexible *)
      if name = Path.string_of_name f then ns else find_dir path ps
    | (Error _ | File _) :: ps, _ ->
      find_dir path ps

  let rec add (path : Path.t) (data : 'a) (root : 'a root) : 'a root =
    match root, path with
    | [], ([], None) ->
      raise (Invalid_argument "OCamlRes.Res.add")
    | [], ([], Some n) ->
      [ File (Path.string_of_name n, data) ]
    | [], (d :: ds, f) ->
      [ Dir (d, add (ds, f) data []) ]
    | (Dir (n, _) | File (n, _)) :: _, ([d], None) when n = d ->
      raise (Failure "OCamlRes.Res.add: already exists")
    | (Dir (n, _) | File (n, _)) :: _, ([], Some f)
      when n = (Path.string_of_name f) ->
      raise (Failure "OCamlRes.Res.add: already exists")
    | Dir (name, ns) as dir :: ps, (d :: ds, f) ->
      if name = d then
        [ Dir (name, add (ds, f) data ns) ]
      else dir :: add path data ps
    | first :: ps, _ ->
      first :: add path data ps

  let rec add_prefix path node =
    match path with
    | [] -> node
    | dir :: path -> Dir (dir, [add_prefix path node])

end
OCaml

Innovation. Community. Security.