···11+22+- `Fpath.relativize` improve docs and change some correct but
33+ surpising results. Thanks to Martin Jambon for the report (#20).
44+55+v0.7.3 2020-09-08 Zagreb
66+------------------------
77+88+- Require OCaml 4.03 and drop `result` compatibility package
99+- Support OCaml 4.12 injectiviy annotation of Map.S (#18).
1010+ Thanks to Kate for the patch.
1111+1212+v0.7.2 2017-05-04 La Forclaz (VS)
1313+---------------------------------
1414+1515+- Fix `odoc` documentation generation.
1616+- Document the error message of `Fpath.of_string` so that
1717+ client can rely and build on it.
1818+1919+v0.7.1 2016-07-12 Cambridge (UK)
2020+--------------------------------
2121+2222+- Add `Fpath.mem_ext`.
2323+- Documentation fixes.
2424+2525+2626+v0.7.0 2016-05-23 La Forclaz (VS)
2727+---------------------------------
2828+2929+First release. Many thanks to David Sheets for his review of the API.
+5
vendor/opam/fpath/DEVEL.md
···11+This project uses [`b0`] for development. Consult [b0 occasionally]
22+for quick hints on how to test and run builds.
33+44+[`b0`]: https://erratique.ch/software/b0
55+[b0 occasionally]: https://erratique.ch/software/b0/doc/occasionally.html
+13
vendor/opam/fpath/LICENSE.md
···11+Copyright (c) 2014 The fpath programmers
22+33+Permission to use, copy, modify, and/or distribute this software for any
44+purpose with or without fee is hereby granted, provided that the above
55+copyright notice and this permission notice appear in all copies.
66+77+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
88+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
99+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1010+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1111+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1212+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1313+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+32
vendor/opam/fpath/README.md
···11+Fpath — File system paths for OCaml
22+===================================
33+44+Fpath is an OCaml module for handling file system paths with POSIX or
55+Windows conventions. Fpath processes paths without accessing the file
66+system and is independent from any system library.
77+88+Fpath depends on [Astring] and is distributed under the ISC license.
99+1010+[astring]: http://erratique.ch/software/astring
1111+1212+Home page: <http://erratique.ch/software/fpath>
1313+1414+## Installation
1515+1616+Fpath can be installed with `opam`:
1717+1818+ opam install fpath
1919+2020+If you don't use `opam` consult the [`opam`](opam) file for build
2121+instructions.
2222+2323+## Documentation
2424+2525+The documentation can be consulted [online] or via `odig doc fpath`.
2626+2727+Questions are welcome but better asked on the [OCaml forum] than on
2828+the issue tracker.
2929+3030+[online]: https://erratique.ch/software/fpath
3131+[OCaml forum]: https://discuss.ocaml.org/
3232+
+5
vendor/opam/fpath/_tags
···11+true : bin_annot, safe_string, package(astring)
22+<_b0> : -traverse
33+<src> : include
44+<src/top/fpath_top*> : package(compiler-libs.toplevel)
55+<test> : include
+11
vendor/opam/fpath/doc/index.mld
···11+{0 Fpath {%html: <span class="version">%%VERSION%%</span>%}}
22+33+Fpath handles file system paths with POSIX or Windows conventions. It
44+processes paths without accessing the file system and is independent
55+from any system library.
66+77+{1:api API}
88+99+{!modules:
1010+Fpath
1111+}
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2015 The fpath programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+open Astring
77+88+(* Unsafe string and byte manipulations. If you don't believe the
99+ author's invariants, replacing with safe versions makes everything
1010+ safe in the library. He won't be upset. *)
1111+1212+let bytes_unsafe_set = Bytes.unsafe_set
1313+let string_unsafe_get = String.unsafe_get
1414+1515+(* Errors *)
1616+1717+let err_invalid_seg s = strf "%a: invalid segment" String.dump s
1818+let err_invalid_ext s = strf "%a: invalid extension" String.dump s
1919+2020+(* A few useful constants *)
2121+2222+let windows = Sys.os_type = "Win32"
2323+let dir_sep_char = if windows then '\\' else '/'
2424+let dir_sep = String.of_char dir_sep_char
2525+let dir_sep_sub = String.sub dir_sep
2626+let not_dir_sep c = c <> dir_sep_char
2727+2828+let dot = "."
2929+let dot_dir = dot ^ dir_sep
3030+let dot_dir_sub = String.sub dot_dir
3131+let dotdot = ".."
3232+let dotdot_dir = dotdot ^ dir_sep
3333+let dotdot_dir_sub = String.sub dotdot_dir
3434+3535+(* Platform specific preliminaties *)
3636+3737+module Windows = struct
3838+3939+ let is_unc_path p = String.is_prefix ~affix:"\\\\" p
4040+ let has_drive p = String.exists (Char.equal ':') p
4141+ let non_unc_path_start p = match String.find (Char.equal ':') p with
4242+ | None -> 0
4343+ | Some i -> i + 1 (* exists by construction *)
4444+4545+ let parse_unc s =
4646+ (* parses an UNC path, the \\ prefix was already parsed, adds a root path
4747+ if there's only a volume, UNC paths are always absolute. *)
4848+ let p = String.sub ~start:2 s in
4949+ let not_bslash c = c <> '\\' in
5050+ let parse_seg p = String.Sub.span ~min:1 ~sat:not_bslash p in
5151+ let ensure_root r = Some (if String.Sub.is_empty r then (s ^ "\\") else s)
5252+ in
5353+ match parse_seg p with
5454+ | (seg1, _) when String.Sub.is_empty seg1 -> None (* \\ or \\\ *)
5555+ | (seg1, rest) ->
5656+ let seg1_len = String.Sub.length seg1 in
5757+ match String.Sub.get_head ~rev:true seg1 with
5858+ | '.' when seg1_len = 1 -> (* \\.\device\ *)
5959+ begin match parse_seg (String.Sub.tail rest) with
6060+ | (seg, _) when String.Sub.is_empty seg -> None
6161+ | (_, rest) -> ensure_root rest
6262+ end
6363+ | '?' when seg1_len = 1 ->
6464+ begin match parse_seg (String.Sub.tail rest) with
6565+ | (seg2, _) when String.Sub.is_empty seg2 -> None
6666+ | (seg2, rest) ->
6767+ if (String.Sub.get_head ~rev:true seg2 = ':') (* \\?\drive:\ *)
6868+ then (ensure_root rest) else
6969+ if not (String.Sub.equal_bytes seg2 (String.sub "UNC"))
7070+ then begin (* \\?\server\share\ *)
7171+ match parse_seg (String.Sub.tail rest) with
7272+ | (seg, _) when String.Sub.is_empty seg -> None
7373+ | (_, rest) -> ensure_root rest
7474+ end else begin (* \\?\UNC\server\share\ *)
7575+ match parse_seg (String.Sub.tail rest) with
7676+ | (seg, _) when String.Sub.is_empty seg -> None
7777+ | (_, rest) ->
7878+ match parse_seg (String.Sub.tail rest) with
7979+ | (seg, _) when String.Sub.is_empty seg -> None
8080+ | (_, rest) -> ensure_root rest
8181+ end
8282+ end
8383+ | _ -> (* \\server\share\ *)
8484+ begin match parse_seg (String.Sub.tail rest) with
8585+ | (seg, _) when String.Sub.is_empty seg -> None
8686+ | (_, rest) -> ensure_root rest
8787+ end
8888+8989+ let sub_split_volume p =
9090+ (* splits a windows path into its volume (or drive) and actual file
9191+ path. When called the path in [p] is guaranteed to be non empty
9292+ and if [p] is an UNC path it is guaranteed to the be parseable by
9393+ parse_unc_windows. *)
9494+ let split_before i = String.sub p ~stop:i, String.sub p ~start:i in
9595+ if not (is_unc_path p) then
9696+ begin match String.find (Char.equal ':') p with
9797+ | None -> String.Sub.empty, String.sub p
9898+ | Some i -> split_before (i + 1)
9999+ end
100100+ else
101101+ let bslash ~start = match String.find ~start (Char.equal '\\') p with
102102+ | None -> assert false | Some i -> i
103103+ in
104104+ let i = bslash ~start:2 in
105105+ let j = bslash ~start:(i + 1) in
106106+ match p.[i-1] with
107107+ | '.' when i = 3 -> split_before j
108108+ | '?' when i = 3 ->
109109+ if p.[j-1] = ':' then split_before j else
110110+ if (String.Sub.equal_bytes
111111+ (String.sub p ~start:(i + 1) ~stop:j)
112112+ (String.sub "UNC"))
113113+ then split_before (bslash ~start:((bslash ~start:(j + 1)) + 1))
114114+ else split_before (bslash ~start:(j + 1))
115115+ | _ -> split_before j
116116+117117+ let is_root p =
118118+ let _, path = sub_split_volume p in
119119+ String.Sub.length path = 1 && String.Sub.get path 0 = dir_sep_char
120120+end
121121+122122+module Posix = struct
123123+ let has_volume p = String.is_prefix ~affix:"//" p
124124+ let is_root p = String.equal p dir_sep || String.equal p "//"
125125+end
126126+127127+(* Segments *)
128128+129129+let is_seg_windows s =
130130+ let valid c = c <> '\x00' && c <> dir_sep_char && c <> '/' in
131131+ String.for_all valid s
132132+133133+let is_seg_posix s =
134134+ let valid c = c <> '\x00' && c <> dir_sep_char in
135135+ String.for_all valid s
136136+137137+let is_seg = if windows then is_seg_windows else is_seg_posix
138138+139139+let _split_last_seg p = String.Sub.span ~rev:true ~sat:not_dir_sep p
140140+let _sub_last_seg p = String.Sub.take ~rev:true ~sat:not_dir_sep p
141141+let _sub_last_non_empty_seg p = (* returns empty on roots though *)
142142+ let dir, last = _split_last_seg p in
143143+ match String.Sub.is_empty last with
144144+ | false -> last
145145+ | true -> _sub_last_seg (String.Sub.tail ~rev:true dir)
146146+147147+let _split_last_non_empty_seg p =
148148+ let (dir, last_seg as r) = _split_last_seg p in
149149+ match String.Sub.is_empty last_seg with
150150+ | false -> r, true
151151+ | true -> _split_last_seg (String.Sub.tail ~rev:true dir), false
152152+153153+let sub_last_seg_windows p = _sub_last_seg (snd (Windows.sub_split_volume p))
154154+let sub_last_seg_posix p = _sub_last_seg (String.sub p)
155155+let sub_last_seg = if windows then sub_last_seg_windows else sub_last_seg_posix
156156+157157+let sub_last_non_empty_seg_windows p =
158158+ _sub_last_non_empty_seg (snd (Windows.sub_split_volume p))
159159+160160+let sub_last_non_empty_seg_posix p =
161161+ _sub_last_non_empty_seg (String.sub p)
162162+163163+let sub_last_non_empty_seg =
164164+ if windows then sub_last_non_empty_seg_windows else
165165+ sub_last_non_empty_seg_posix
166166+167167+let is_rel_seg = function "." | ".." -> true | _ -> false
168168+169169+let sub_is_rel_seg seg = match String.Sub.length seg with
170170+| 1 when String.Sub.get seg 0 = '.' -> true
171171+| 2 when String.Sub.get seg 0 = '.' && String.Sub.get seg 1 = '.' -> true
172172+| _ -> false
173173+174174+let sub_is_dir_seg seg = match String.Sub.length seg with
175175+| 0 -> true
176176+| 1 when String.Sub.get seg 0 = '.' -> true
177177+| 2 when String.Sub.get seg 0 = '.' && String.Sub.get seg 1 = '.' -> true
178178+| _ -> false
179179+180180+let segs_of_path p = String.cuts ~sep:dir_sep p
181181+let segs_to_path segs = String.concat ~sep:dir_sep segs
182182+183183+(* File paths *)
184184+185185+type t = string (* N.B. a path is never "" or something is wrooong. *)
186186+187187+let err s = Error (`Msg (strf "%a: invalid path" String.dump s))
188188+189189+let validate_and_collapse_seps p =
190190+ (* collapse non-initial sequences of [dir_sep] to a single one and checks
191191+ no null byte *)
192192+ let max_idx = String.length p - 1 in
193193+ let rec with_buf b last_sep k i = (* k is the write index in b *)
194194+ if i > max_idx then Ok (Bytes.sub_string b 0 k) else
195195+ let c = string_unsafe_get p i in
196196+ if c = '\x00' then err p else
197197+ if c <> dir_sep_char
198198+ then (bytes_unsafe_set b k c; with_buf b false (k + 1) (i + 1)) else
199199+ if not last_sep
200200+ then (bytes_unsafe_set b k c; with_buf b true (k + 1) (i + 1)) else
201201+ with_buf b true k (i + 1)
202202+ in
203203+ let rec try_no_alloc last_sep i =
204204+ if i > max_idx then Ok p else
205205+ let c = string_unsafe_get p i in
206206+ if c = '\x00' then err p else
207207+ if c <> dir_sep_char then try_no_alloc false (i + 1) else
208208+ if not last_sep then try_no_alloc true (i + 1) else
209209+ let b = Bytes.of_string p in (* copy and overwrite starting from i *)
210210+ with_buf b true i (i + 1)
211211+ in
212212+ let start = (* Allow initial double sep for POSIX and UNC paths *)
213213+ if max_idx > 0 then (if p.[0] = dir_sep_char then 1 else 0) else 0
214214+ in
215215+ try_no_alloc false start
216216+217217+let of_string_windows s =
218218+ if s = "" then err s else
219219+ let p = String.map (fun c -> if c = '/' then '\\' else c) s in
220220+ match validate_and_collapse_seps p with
221221+ | Error _ as e -> e
222222+ | Ok p as some ->
223223+ if Windows.is_unc_path p then
224224+ (match Windows.parse_unc p with None -> err s | Some p -> Ok p)
225225+ else
226226+ match String.find (Char.equal ':') p with
227227+ | None -> some
228228+ | Some i when i = String.length p - 1 -> err p (* path is empty *)
229229+ | Some _ -> Ok p
230230+231231+let of_string_posix p = if p = "" then err p else validate_and_collapse_seps p
232232+let of_string = if windows then of_string_windows else of_string_posix
233233+234234+let v s = match of_string s with
235235+| Ok p -> p
236236+| Error (`Msg m) -> invalid_arg m
237237+238238+let add_seg p seg =
239239+ if not (is_seg seg) then invalid_arg (err_invalid_seg seg);
240240+ let sep = if p.[String.length p - 1] = dir_sep_char then "" else dir_sep in
241241+ String.concat ~sep [p; seg]
242242+243243+let append_posix p0 p1 =
244244+ if p1.[0] = dir_sep_char (* absolute *) then p1 else
245245+ let sep = if p0.[String.length p0 - 1] = dir_sep_char then "" else dir_sep in
246246+ String.concat ~sep [p0; p1]
247247+248248+let append_windows p0 p1 =
249249+ if Windows.is_unc_path p1 || Windows.has_drive p1 then p1 else
250250+ if p1.[0] = dir_sep_char then (* absolute *) p1 else
251251+ let sep = if p0.[String.length p0 - 1] = dir_sep_char then "" else dir_sep in
252252+ String.concat ~sep [p0; p1]
253253+254254+let append = if windows then append_windows else append_posix
255255+256256+let ( / ) = add_seg
257257+let ( // ) = append
258258+259259+let split_volume_windows p =
260260+ let vol, path = Windows.sub_split_volume p in
261261+ String.Sub.to_string vol, String.Sub.to_string path
262262+263263+let split_volume_posix p =
264264+ if Posix.has_volume p then dir_sep, String.with_range ~first:1 p else "", p
265265+266266+let split_volume = if windows then split_volume_windows else split_volume_posix
267267+268268+let segs_windows p =
269269+ let _, path = Windows.sub_split_volume p in
270270+ segs_of_path (String.Sub.to_string path)
271271+272272+let segs_posix p =
273273+ let segs = segs_of_path p in
274274+ if Posix.has_volume p then List.tl segs else segs
275275+276276+let segs = if windows then segs_windows else segs_posix
277277+278278+(* File and directory paths *)
279279+280280+let is_dir_path p = sub_is_dir_seg (sub_last_seg p)
281281+let is_file_path p = not (is_dir_path p)
282282+let to_dir_path p = add_seg p ""
283283+284284+let filename p = match String.Sub.to_string (sub_last_seg p) with
285285+| "" | "." | ".." -> ""
286286+| filename -> filename
287287+288288+(* Base and parent paths *)
289289+290290+let sub_is_root p = String.Sub.length p = 1 && String.Sub.get p 0 = dir_sep_char
291291+292292+let _split_base p =
293293+ let dir, last_seg = _split_last_seg p in
294294+ match String.Sub.is_empty dir with
295295+ | true -> (* single seg *) dot_dir_sub, String.Sub.to_string p
296296+ | false ->
297297+ match String.Sub.is_empty last_seg with
298298+ | false -> dir, String.Sub.to_string last_seg
299299+ | true ->
300300+ let dir_file = String.Sub.tail ~rev:true dir in
301301+ let dir, dir_last_seg = _split_last_seg dir_file in
302302+ match String.Sub.is_empty dir with
303303+ | true -> dot_dir_sub, String.Sub.to_string p
304304+ | false -> dir, String.Sub.(to_string (extend dir_last_seg))
305305+306306+let split_base_windows p =
307307+ let vol, path = Windows.sub_split_volume p in
308308+ if sub_is_root path then p, dot_dir else
309309+ let dir, b = _split_base path in
310310+ String.Sub.(base_string (append vol dir)), b
311311+312312+let split_base_posix p =
313313+ if Posix.is_root p then p, dot_dir else
314314+ let dir, b = _split_base (String.sub p) in
315315+ String.Sub.to_string dir, b
316316+317317+let split_base = if windows then split_base_windows else split_base_posix
318318+319319+let base p = snd (split_base p)
320320+321321+let _basename p = match String.Sub.to_string (_sub_last_non_empty_seg p) with
322322+| "." | ".." -> ""
323323+| basename -> basename
324324+325325+let basename_windows p =
326326+ let vol, path = Windows.sub_split_volume p in
327327+ if sub_is_root path then "" else _basename path
328328+329329+let basename_posix p = if Posix.is_root p then "" else _basename (String.sub p)
330330+let basename p = if windows then basename_windows p else basename_posix p
331331+332332+let _parent p =
333333+ (* The parent algorithm is not very smart. It tries to preserve the
334334+ original path and avoids dealing with normalization. We simply
335335+ only keep everything before the last non-empty, non-relative,
336336+ path segment and if the resulting path is empty we return
337337+ "./". Otherwise if the last non-empty segment is "." or ".." we
338338+ simply postfix with "../" *)
339339+ let (dir, seg), is_last = _split_last_non_empty_seg p in
340340+ let dsep = if is_last then dir_sep_sub else String.Sub.empty in
341341+ if sub_is_rel_seg seg then [p; dsep; dotdot_dir_sub] else
342342+ if String.Sub.is_empty dir then [dot_dir_sub] else [dir]
343343+344344+let parent_windows p =
345345+ let vol, path = Windows.sub_split_volume p in
346346+ if sub_is_root path then p else
347347+ String.Sub.(base_string @@ concat (vol :: _parent path))
348348+349349+let parent_posix p =
350350+ if Posix.is_root p then p else
351351+ String.Sub.(base_string @@ concat (_parent (String.sub p)))
352352+353353+let parent = if windows then parent_windows else parent_posix
354354+355355+(* Normalization *)
356356+357357+let rem_empty_seg_windows p =
358358+ let vol, path = Windows.sub_split_volume p in
359359+ if sub_is_root path then p else
360360+ let max = String.Sub.stop_pos path - 1 in
361361+ if String.get p max <> dir_sep_char then p else
362362+ String.with_index_range p ~last:(max - 1)
363363+364364+let rem_empty_seg_posix p = match String.length p with
365365+| 1 -> p
366366+| 2 ->
367367+ if p.[0] <> dir_sep_char && p.[1] = dir_sep_char
368368+ then String.of_char p.[0]
369369+ else p
370370+| len ->
371371+ let max = len - 1 in
372372+ if p.[max] <> dir_sep_char then p else
373373+ String.with_index_range p ~last:(max - 1)
374374+375375+let rem_empty_seg =
376376+ if windows then rem_empty_seg_windows else rem_empty_seg_posix
377377+378378+let normalize_rel_segs segs = (* result is non empty but may be [""] *)
379379+ let rec loop acc = function
380380+ | "." :: [] -> ("" :: acc) (* final "." remove but preserve directoryness. *)
381381+ | "." :: rest -> loop acc rest
382382+ | ".." :: rest ->
383383+ begin match acc with
384384+ | ".." :: _ | [] -> loop (".." :: acc) rest
385385+ | seg :: acc -> (* N.B. seg can't be "." *)
386386+ match rest with
387387+ | [] -> ("" :: acc) (* preserve directoryness *)
388388+ | rest -> loop acc rest
389389+ end
390390+ | seg :: rest -> loop (seg :: acc) rest
391391+ | [] ->
392392+ match acc with
393393+ | ".." :: _ -> ("" :: acc) (* normalize final .. to ../ *)
394394+ | [] -> [""]
395395+ | acc -> acc
396396+ in
397397+ List.rev (loop [] segs)
398398+399399+let normalize_segs = function
400400+| "" :: segs -> (* absolute path *)
401401+ let rec rem_dotdots = function ".." :: ss -> rem_dotdots ss | ss -> ss in
402402+ "" :: (rem_dotdots @@ normalize_rel_segs segs)
403403+| segs ->
404404+ match normalize_rel_segs segs with
405405+ | [""] -> ["."; ""]
406406+ | segs -> segs
407407+408408+let normalize_windows p =
409409+ let vol, path = Windows.sub_split_volume p in
410410+ let path = String.Sub.to_string path in
411411+ let path = segs_to_path @@ normalize_segs (segs_of_path path) in
412412+ String.Sub.(to_string (concat [vol; String.sub path]))
413413+414414+let normalize_posix p =
415415+ let has_volume = Posix.has_volume p in
416416+ let segs = segs_of_path p in
417417+ let segs = normalize_segs @@ if has_volume then List.tl segs else segs in
418418+ let segs = if has_volume then "" :: segs else segs in
419419+ segs_to_path segs
420420+421421+let normalize = if windows then normalize_windows else normalize_posix
422422+423423+(* Prefixes *)
424424+425425+let is_prefix prefix p =
426426+ if not (String.is_prefix ~affix:prefix p) then false else
427427+ (* Further check the prefix is segment-based. If [prefix] ends with a
428428+ dir_sep_char nothing more needs to be checked. If it doesn't we need
429429+ to check that [p]'s remaining suffix is either empty or
430430+ starts with a directory separator. *)
431431+ let suff_start = String.length prefix in
432432+ if prefix.[suff_start - 1] = dir_sep_char then true else
433433+ if suff_start = String.length p then (* suffix empty *) true else
434434+ p.[suff_start] = dir_sep_char
435435+436436+let _prefix_last_index p0 p1 = (* last char index of segment-based prefix *)
437437+ let l0 = String.length p0 in
438438+ let l1 = String.length p1 in
439439+ let p0, p1, max = if l0 < l1 then p0, p1, l0 - 1 else p1, p0, l1 - 1 in
440440+ let rec loop last_dir_sep i p0 p1 = match i > max || p0.[i] <> p1.[i] with
441441+ | false ->
442442+ let last_dir_sep = if p0.[i] = dir_sep_char then i else last_dir_sep in
443443+ loop last_dir_sep (i + 1) p0 p1
444444+ | true ->
445445+ if i = 0 then None else
446446+ let last = i - 1 in
447447+ if last_dir_sep = last then Some last else
448448+ match last = max with
449449+ | true ->
450450+ if l1 = l0 then Some last else
451451+ if p1.[i] = dir_sep_char then Some last else
452452+ if last_dir_sep <> -1 then Some last_dir_sep else None
453453+ | false ->
454454+ if last_dir_sep <> -1 then Some last_dir_sep else None
455455+ in
456456+ loop (-1) 0 p0 p1
457457+458458+let find_prefix_windows p0 p1 = match _prefix_last_index p0 p1 with
459459+| None -> None
460460+| Some i ->
461461+ let v0_len = String.Sub.length (fst (Windows.sub_split_volume p0)) in
462462+ let v1_len = String.Sub.length (fst (Windows.sub_split_volume p1)) in
463463+ let max_vlen = if v0_len > v1_len then v0_len else v1_len in
464464+ if i < max_vlen then None else Some (String.with_index_range p0 ~last:i)
465465+466466+let find_prefix_posix p0 p1 = match _prefix_last_index p0 p1 with
467467+| None -> None
468468+| Some 0 when Posix.has_volume p0 || Posix.has_volume p1 -> None
469469+| Some i -> Some (String.with_index_range p0 ~last:i)
470470+471471+let find_prefix = if windows then find_prefix_windows else find_prefix_posix
472472+473473+let rem_prefix prefix p = match is_prefix prefix p with
474474+| false -> None
475475+| true ->
476476+ match String.length prefix with
477477+ | len when len = String.length p -> None
478478+ | len ->
479479+ let first = if p.[len] = dir_sep_char then len + 1 else len in
480480+ match String.with_index_range p ~first with
481481+ | "" -> Some dot_dir
482482+ | q -> Some q
483483+484484+(* Roots and relativization *)
485485+486486+let _relativize ~root p =
487487+ if String.equal root p
488488+ then Some (segs_to_path (if is_dir_path p then ["."; ""] else ["."])) else
489489+ let root = (* root is always interpreted as a directory *)
490490+ let root = normalize root in
491491+ if root.[String.length root - 1] = dir_sep_char then root else
492492+ root ^ dir_sep
493493+ in
494494+ let p = normalize p in
495495+ let rec walk root p = match root, p with
496496+ | (".." :: _, s :: _) when s <> ".." ->
497497+ (* [root] has too many up segments. Cannot walk down to express [p],
498498+ e.g. "../a" can't be expressed relative to "../../". *)
499499+ None
500500+ | (sr :: root, sp :: (_ :: _ as p)) when sr = sp ->
501501+ (* the next directory in [root] and [p] match and it's not the last
502502+ segment of [p], walk to next segment *)
503503+ walk root p
504504+ | [""], [""] ->
505505+ (* walk ends at the end of both path simultaneously, [p] is a
506506+ directory that matches exactly [root] interpreted as a directory. *)
507507+ Some (segs_to_path ["."; ""])
508508+ | root, p ->
509509+ (* walk ends here, either the next directory is different in
510510+ [root] and [p] or it is equal but it is the last one for [p]
511511+ and different from [""] (i.e. [p] is a file path and prefix
512512+ of [root]). To get to the current position from the remaining
513513+ root we need to go up the number of non-empty segments that
514514+ remain in [root] (length root - 1). To get to the path [p]
515515+ from the current position we just use [p] so prepending
516516+ length root - 1 ".." segments to [p] tells us how to go from
517517+ the remaining root to [p]. *)
518518+ let segs = List.fold_left (fun acc _ -> dotdot :: acc) p (List.tl root) in
519519+ Some (segs_to_path segs)
520520+ in
521521+ match segs root, segs p with
522522+ | ("" :: _, s :: _) when s <> "" -> None (* absolute/relative mismatch *)
523523+ | (s :: _, "" :: _) when s <> "" -> None (* absolute/relative mismatch *)
524524+ | ["."; ""], p ->
525525+ (* p is relative and must be expressed w.r.t. "./", so it is itself. *)
526526+ Some (segs_to_path p)
527527+ | root, p ->
528528+ (* walk in the segments of root and p until a segment mismatches.
529529+ at that point express the remaining p relative to the remaining
530530+ root. Note that because of normalization both [root] and [p] may
531531+ only have initial .. segments and [root] by construction has a
532532+ final "" segment. *)
533533+ walk root p
534534+535535+let relativize_windows ~root p =
536536+ let rvol, root = Windows.sub_split_volume root in
537537+ let pvol, p = Windows.sub_split_volume p in
538538+ if not (String.Sub.equal_bytes rvol pvol) then None else
539539+ let root = String.Sub.to_string root in
540540+ let p = String.Sub.to_string p in
541541+ _relativize ~root p
542542+543543+let relativize_posix ~root p = _relativize ~root p
544544+545545+let relativize = if windows then relativize_windows else relativize_posix
546546+547547+let is_rooted ~root p = match relativize ~root p with
548548+| None -> false
549549+| Some r ->
550550+ not (String.equal dotdot r || String.is_prefix ~affix:dotdot_dir r ||
551551+ (String.equal root p && not (is_dir_path p)))
552552+553553+(* Predicates and comparison *)
554554+555555+let is_rel_posix p = p.[0] <> dir_sep_char
556556+let is_rel_windows p =
557557+ if Windows.is_unc_path p then false else
558558+ p.[Windows.non_unc_path_start p] <> dir_sep_char
559559+560560+let is_rel = if windows then is_rel_windows else is_rel_posix
561561+let is_abs p = not (is_rel p)
562562+let is_root = if windows then Windows.is_root else Posix.is_root
563563+564564+let is_current_dir_posix ?(prefix = false) p = match prefix with
565565+| false -> String.equal dot p || String.equal dot_dir p
566566+| true -> String.equal dot p || String.is_prefix ~affix:dot_dir p
567567+568568+let is_current_dir_windows ?(prefix = false) p =
569569+ if Windows.is_unc_path p then false else
570570+ let start = Windows.non_unc_path_start p in
571571+ match String.length p - start with
572572+ | 1 -> p.[start] = '.'
573573+ | n when n = 2 || prefix -> p.[start] = '.' && p.[start + 1] = dir_sep_char
574574+ | _ -> false
575575+576576+let is_current_dir =
577577+ if windows then is_current_dir_windows else is_current_dir_posix
578578+579579+let is_parent_dir_posix ?(prefix = false) p = match prefix with
580580+| false -> String.equal dotdot p || String.equal dotdot_dir p
581581+| true -> String.equal dotdot p || String.is_prefix ~affix:dotdot_dir p
582582+583583+let is_parent_dir_windows ?(prefix = false) p =
584584+ if Windows.is_unc_path p then false else
585585+ let start = Windows.non_unc_path_start p in
586586+ match String.length p - start with
587587+ | 1 -> false
588588+ | 2 -> p.[start] = '.' && p.[start + 1] = '.'
589589+ | n when n = 3 || prefix ->
590590+ p.[start] = '.' && p.[start + 1] = '.' && p.[start + 2] = dir_sep_char
591591+ | _ -> false
592592+593593+let is_parent_dir =
594594+ if windows then is_parent_dir_windows else is_parent_dir_posix
595595+596596+let is_dotfile p = match basename p with | "" -> false | s -> s.[0] = '.'
597597+598598+let equal = String.equal
599599+let compare = String.compare
600600+601601+(* Conversions and pretty printing *)
602602+603603+let to_string p = p
604604+let pp ppf p = Format.pp_print_string ppf (to_string p)
605605+let dump ppf p = String.dump ppf (to_string p)
606606+607607+(* File extensions *)
608608+609609+type ext = string
610610+611611+let ext_sep_char = '.'
612612+let ext_sep = String.of_char ext_sep_char
613613+let ext_sep_sub = String.Sub.of_char ext_sep_char
614614+let eq_ext_sep c = c = ext_sep_char
615615+let neq_ext_sep c = c <> ext_sep_char
616616+617617+let rec sub_multi_ext seg =
618618+ let first_not_sep = String.Sub.drop ~sat:eq_ext_sep seg in
619619+ String.Sub.drop ~sat:neq_ext_sep first_not_sep
620620+621621+let sub_single_ext seg =
622622+ let name_dot, ext = String.Sub.span ~rev:true ~sat:neq_ext_sep seg in
623623+ if String.Sub.exists neq_ext_sep name_dot
624624+ then String.Sub.extend ~max:1 ~rev:true ext
625625+ else String.Sub.empty
626626+627627+let sub_ext ?(multi = false) seg =
628628+ if multi then sub_multi_ext seg else sub_single_ext seg
629629+630630+let sub_get_ext ?multi p = sub_ext ?multi (sub_last_non_empty_seg p)
631631+let get_ext ?multi p = String.Sub.to_string (sub_get_ext ?multi p)
632632+633633+let has_ext e p =
634634+ let ext = sub_get_ext ~multi:true p in
635635+ if String.Sub.is_empty ext then false else
636636+ if not (String.(Sub.is_suffix ~affix:(sub e) ext)) then false else
637637+ if not (String.is_empty e) && e.[0] = ext_sep_char then true else
638638+ (* Check there's a dot before the suffix [e] in [ext] *)
639639+ let dot_index = String.Sub.length ext - String.length e - 1 in
640640+ String.Sub.get ext dot_index = ext_sep_char
641641+642642+let mem_ext exts p = List.exists (fun ext -> has_ext ext p) exts
643643+644644+let exists_ext ?(multi = false) p =
645645+ let ext = sub_get_ext ~multi p in
646646+ if multi then String.Sub.exists eq_ext_sep (String.Sub.tail ext) else
647647+ not (String.Sub.is_empty ext)
648648+649649+let add_ext e p =
650650+ if String.is_empty e then p else
651651+ if not (is_seg e) then invalid_arg (err_invalid_ext e) else
652652+ let seg = sub_last_non_empty_seg p in
653653+ if sub_is_dir_seg seg then p else
654654+ let e_has_dot = e.[0] = ext_sep_char in
655655+ let maybe_dot = if e_has_dot then String.Sub.empty else ext_sep_sub in
656656+ let has_empty = p.[String.length p - 1] = dir_sep_char in
657657+ let maybe_empty = if has_empty then dir_sep_sub else String.Sub.empty in
658658+ let seg_end = String.Sub.stop_pos seg - 1 in
659659+ let prefix = String.sub_with_index_range ~last:seg_end p in
660660+ let path = [prefix; maybe_dot; String.sub e; maybe_empty] in
661661+ String.Sub.(base_string (concat path))
662662+663663+let _split_ext ?multi p =
664664+ let ext = sub_get_ext ?multi p in
665665+ if String.Sub.is_empty ext then p, ext else
666666+ let before_ext = String.Sub.start_pos ext - 1 in
667667+ if String.Sub.stop_pos ext = String.length p
668668+ then String.with_index_range p ~last:before_ext, ext else
669669+ let prefix = String.sub_with_index_range p ~last:before_ext in
670670+ String.Sub.(base_string (concat [prefix; dir_sep_sub])), ext
671671+672672+let rem_ext ?multi p = fst (_split_ext ?multi p)
673673+let set_ext ?multi e p = add_ext e (rem_ext ?multi p)
674674+let split_ext ?multi p =
675675+ let p, ext = _split_ext ?multi p in
676676+ p, String.Sub.to_string ext
677677+678678+let ( + ) p e = add_ext e p
679679+let ( -+ ) p e = set_ext e p
680680+681681+(* Path sets and maps *)
682682+683683+type path = t
684684+685685+module Set = struct
686686+ include Set.Make (String)
687687+688688+ let pp ?sep:(pp_sep = Format.pp_print_cut) pp_elt ppf ps =
689689+ let pp_elt elt is_first =
690690+ if is_first then () else pp_sep ppf ();
691691+ Format.fprintf ppf "%a" pp_elt elt; false
692692+ in
693693+ ignore (fold pp_elt ps true)
694694+695695+ let dump_path = dump
696696+ let dump ppf ss =
697697+ let pp_elt elt is_first =
698698+ if is_first then () else Format.fprintf ppf "@ ";
699699+ Format.fprintf ppf "%a" dump_path elt;
700700+ false
701701+ in
702702+ Format.fprintf ppf "@[<1>{";
703703+ ignore (fold pp_elt ss true);
704704+ Format.fprintf ppf "}@]";
705705+ ()
706706+707707+ let err_empty () = invalid_arg "empty set"
708708+ let err_absent p ps =
709709+ invalid_arg (strf "%a not in set %a" dump_path p dump ps)
710710+711711+ let get_min_elt ps = try min_elt ps with Not_found -> err_empty ()
712712+ let min_elt ps = try Some (min_elt ps) with Not_found -> None
713713+714714+ let get_max_elt ps = try max_elt ps with Not_found -> err_empty ()
715715+ let max_elt ps = try Some (max_elt ps) with Not_found -> None
716716+717717+ let get_any_elt ps = try choose ps with Not_found -> err_empty ()
718718+ let choose ps = try Some (choose ps) with Not_found -> None
719719+720720+ let get p ps = try find p ps with Not_found -> err_absent p ps
721721+ let find p ps = try Some (find p ps) with Not_found -> None
722722+723723+ let of_list = List.fold_left (fun acc s -> add s acc) empty
724724+end
725725+726726+module Map = struct
727727+ include Map.Make (String)
728728+729729+ let err_empty () = invalid_arg "empty map"
730730+ let err_absent s = invalid_arg (strf "%s is not bound in map" s)
731731+732732+ let get_min_binding m = try min_binding m with Not_found -> err_empty ()
733733+ let min_binding m = try Some (min_binding m) with Not_found -> None
734734+735735+ let get_max_binding m = try max_binding m with Not_found -> err_empty ()
736736+ let max_binding m = try Some (max_binding m) with Not_found -> None
737737+738738+ let get_any_binding m = try choose m with Not_found -> err_empty ()
739739+ let choose m = try Some (choose m) with Not_found -> None
740740+741741+ let get k s = try find k s with Not_found -> err_absent k
742742+ let find k m = try Some (find k m) with Not_found -> None
743743+744744+ let dom m = fold (fun k _ acc -> Set.add k acc) m Set.empty
745745+746746+ let of_list bs = List.fold_left (fun m (k,v) -> add k v m) empty bs
747747+748748+ let pp ?sep:(pp_sep = Format.pp_print_cut) pp_binding ppf (m : 'a t) =
749749+ let pp_binding k v is_first =
750750+ if is_first then () else pp_sep ppf ();
751751+ pp_binding ppf (k, v); false
752752+ in
753753+ ignore (fold pp_binding m true)
754754+755755+ let dump pp_v ppf m =
756756+ let pp_binding k v is_first =
757757+ if is_first then () else Format.fprintf ppf "@ ";
758758+ Format.fprintf ppf "@[<1>(@[%a@],@ @[%a@])@]" dump k pp_v v;
759759+ false
760760+ in
761761+ Format.fprintf ppf "@[<1>{";
762762+ ignore (fold pp_binding m true);
763763+ Format.fprintf ppf "}@]";
764764+ ()
765765+end
766766+767767+type set = Set.t
768768+type 'a map = 'a Map.t
+994
vendor/opam/fpath/src/fpath.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2014 The fpath programmers. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** File system paths, file {{!file_exts}extensions}, path {{!Set}sets}
77+ and {{!Map}maps}.
88+99+ A (file system) {e path} specifies a file or a directory in a file
1010+ system hierarchy. A path has three parts:
1111+ {ol
1212+ {- An optional, platform-dependent, {{!split_volume}volume}.}
1313+ {- An optional root directory separator {!dir_sep} whose presence
1414+ distinguishes {e absolute} paths (["/a"]) from {e relative}
1515+ ones (["a"])}
1616+ {- A non-empty list of {!dir_sep} separated segments. Segments are
1717+ non empty strings except for maybe the last one. The latter
1818+ distinguishes {e directory paths}
1919+ (["a/b/"]) from {e file paths} (["a/b"]).}}
2020+2121+ The path segments ["."] and [".."] are {{!is_rel_seg}{e relative
2222+ path segments}} that respectively denote the current and parent
2323+ directory. The {{!basename}{e basename}} of a path is its last
2424+ non-empty segment if it is not a relative path segment or the empty
2525+ string otherwise.
2626+2727+ Consult a few {{!tips}important tips}.
2828+2929+ {b Note.} [Fpath] processes paths without accessing the file system. *)
3030+3131+(** {1:segs Separators and segments} *)
3232+3333+val dir_sep : string
3434+(** [dir_sep] is the platform dependent natural directory separator. This is
3535+ ["/"] on POSIX and ["\\"] on Windows. *)
3636+3737+val is_seg : string -> bool
3838+(** [is_seg s] is [true] iff [s] does not contain {!dir_sep} or ['/'] or
3939+ a [0x00] byte. *)
4040+4141+val is_rel_seg : string -> bool
4242+(** [is_rel_seg s] is true iff [s] is a relative segment, that is
4343+ ["."] or [".."]. *)
4444+4545+(** {1:paths Paths} *)
4646+4747+type t
4848+(** The type for paths. *)
4949+5050+val v : string -> t
5151+(** [v s] is the string [s] as a path.
5252+5353+ @raise Invalid_argument if [s] is not a {{!of_string}valid path}. Use
5454+ {!of_string} to deal with untrusted input. *)
5555+5656+val add_seg : t -> string -> t
5757+(** [add_seg p seg] adds segment [seg] to the segments of [p] if
5858+ [p]'s last segment is non-empty or replaces the last empty
5959+ segment with [seg]. {{!ex_add_seg}Examples}.
6060+6161+ @raise Invalid_argument if {!is_seg}[ seg] is [false]. *)
6262+6363+val ( / ) : t -> string -> t
6464+(** [p / seg] is {!add_seg}[ p seg]. Left associative. *)
6565+6666+val append : t -> t -> t
6767+(** [append p q] appends [q] to [p] as follows:
6868+ {ul
6969+ {- If [q] is absolute or has a non-empty {{!split_volume}volume} then
7070+ [q] is returned.}
7171+ {- Otherwise appends [q]'s segments to [p] using {!add_seg}.}}
7272+ {{!ex_append}Examples}. *)
7373+7474+val ( // ) : t -> t -> t
7575+(** [p // p'] is {!append}[ p p']. Left associative. *)
7676+7777+val split_volume : t -> string * t
7878+(** [split_volume p] is the pair [(vol, q)] where [vol] is
7979+ the platform dependent volume of [p] or the empty string
8080+ if there is none and [q] the path [p] without its volume, that is
8181+ its optional root {!dir_sep} and segments.
8282+8383+ On POSIX if [vol] is non-empty then it
8484+ {{:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_267}can} only be ["/"] (e.g. in [v "//a/b"]). On Windows [vol] may be
8585+ one of the following prefixes parsed before an
8686+ absolute root {!dir_sep}, except in the first case
8787+ where a relative path can follow:
8888+{[
8989+$(drive):
9090+\\$(server)\$(share)
9191+\\?\$(drive):
9292+\\?\$(server)\$(share)
9393+\\?\UNC\$(server)\$(share)
9494+\\.\$(device)
9595+]}
9696+ The following invariant holds:
9797+ {ul
9898+ {- [equal p (v @@ vol ^ (to_string q))]}} *)
9999+100100+val segs : t -> string list
101101+(** [segs p] is [p]'s {e non-empty} list of segments. Absolute paths have an
102102+ initial empty string added, this allows to recover the path's string with
103103+ {!String.concat}[ ~sep:dir_sep]. {{!ex_segs}Examples.}
104104+105105+ The following invariant holds:
106106+ {ul
107107+ {- [equal p (v @@ (fst @@ split_volume p) ^ (String.concat ~sep:dir_sep
108108+ (segs p)))]}} *)
109109+110110+(** {1:filedir File and directory paths}
111111+112112+ {b Note.} The following functions use syntactic semantic properties
113113+ of paths. Given a path, these properties can be different from the one
114114+ your file system attributes to it. *)
115115+116116+val is_dir_path : t -> bool
117117+(** [is_dir_path p] is [true] iff [p] represents a directory. This
118118+ means that [p]'s last segment is either empty ([""]) or
119119+ {{!is_rel_seg}relative}. The property is invariant with respect
120120+ to {{!normalize}normalization}. {{!ex_is_dir_path}Examples}. *)
121121+122122+val is_file_path : t -> bool
123123+(** [is_file_path p] is [true] iff [p] represents a file. This is the
124124+ negation of {!is_dir_path}. This means that [p]'s last segment is
125125+ neither empty ([""]) nor {{!is_rel_seg}relative}. The property is
126126+ invariant with respect to {{!normalize}normalization}.
127127+ {{!ex_is_file_path}Examples}. *)
128128+129129+val to_dir_path : t -> t
130130+(** [to_dir_path p] is {!add_seg}[ p ""]. It ensure that the result
131131+ represents a {{!is_dir_path}directory} and, if converted to a
132132+ string, that it ends with a {!dir_sep}.
133133+ {{!ex_to_dir_path}Examples}. *)
134134+135135+val filename : t -> string
136136+(** [filename p] is the file name of [p]. This is the last segment of
137137+ [p] if [p] is a {{!is_file_path}file path} and the empty string
138138+ otherwise. The result is invariant with respect to
139139+ {{!normalize}normalization}. See also
140140+ {!basename}. {{!ex_filename}Examples}. *)
141141+142142+(** {1:parentbase Base and parent paths} *)
143143+144144+val split_base : t -> t * t
145145+(** [split_base p] splits [p] into a directory [d] and a {e relative}
146146+ base path [b] such that:
147147+ {ul
148148+ {- [b] is a relative path that contains the segments of [p]
149149+ that start at the last non-empty segment. This means
150150+ that [b] has a {e single} non-empty segment, and preserves
151151+ {{!is_dir_path}directoryness} of [p]. If [p] is a
152152+ {{!is_root}root path} there are no such segments and [b]
153153+ is ["./"].}
154154+ {- [d] is a {{!is_dir_path}directory} such that [d // b]
155155+ represents the same path as [p]. They may however differ
156156+ syntactically when converted to a string.}}
157157+ {{!ex_split_base}Examples}.
158158+159159+ {b Note.} {{!normalize}Normalizing} [p] before using the function
160160+ ensures that [b] is a {{!is_rel_seg}relative segment} iff [p] cannot
161161+ be named (like in ["."], ["../../"], ["/"], etc.). *)
162162+163163+val base : t -> t
164164+(** [base p] is [snd (split_base p)]. *)
165165+166166+val basename : t -> string
167167+(** [basename p] is [p]'s last non-empty segment if non-relative or
168168+ the empty string otherwise. The latter occurs only on {{!is_root}root
169169+ paths} and on paths whose last non-empty segment is a
170170+ {{!is_rel_seg}relative segment}. See also {!filename} and
171171+ {!base}. {{!ex_basename}Examples}.
172172+173173+ {b Note.} {{!normalize}Normalizing} [p] before using the function
174174+ ensures the empty string is only returned iff [p] cannot be
175175+ named (like in ["."], ["../../"], ["/"], etc.) *)
176176+177177+val parent : t -> t
178178+(** [parent p] is a {{!is_dir_path}directory path} that contains [p].
179179+ If [p] is a {{!is_root}root path} this is [p] itself.
180180+ {{!ex_parent}Examples}.
181181+182182+ {b Warning.} [parent p // base p] may not represent [p], use
183183+ {!split_base} for this. *)
184184+185185+(** {1:norm Normalization} *)
186186+187187+val rem_empty_seg : t -> t
188188+(** [rem_empty_seg p] removes an existing last empty segment of [p] if [p]
189189+ is not a {{!is_root}root path}. This ensure that if [p] is
190190+ converted to a string it will not have a trailing {!dir_sep}
191191+ unless [p] is a root path. Note that this may affect [p]'s
192192+ {{!is_dir_path}directoryness}. {{!ex_rem_empty_seg}Examples}. *)
193193+194194+val normalize : t -> t
195195+(** [normalize p] is a path that represents the same path as [p],
196196+ {{!is_dir_path}directoryness} included, and that has the following
197197+ properties:
198198+ {ul
199199+ {- If [p] is absolute the resulting path has no ["."] and [".."]
200200+ segments.}
201201+ {- If [p] is relative the resulting path is either ["./"] or
202202+ it has no ["."] segments and [".."] segments may only appear as
203203+ initial segments.}
204204+ {- If [p] is a {{!is_dir_path}directory} it always end with
205205+ an empty segment; this means it doesn't end with ["."] or [".."].}}
206206+ {{!ex_normalize}Examples}.
207207+208208+ {b Warning.} Like file and directory path {{!filedir}functions}
209209+ this function does not consult the file system and is purely
210210+ based on the syntactic semantic of paths which can be different
211211+ from the one of your concrete file system attributes. For example in
212212+ presence of symbolic links the resulting path may not point to the same
213213+ entity. Use the normalization functions of your OS system library to
214214+ ensure correct behaviour with respect to a concrete file system. *)
215215+216216+(** {1:prefixes Prefixes}
217217+218218+ {b Warning.} The syntactic {{!is_prefix}prefix relation} between
219219+ paths does not, in general, entail directory containement. The following
220220+ examples show this:
221221+{[
222222+is_prefix (v "..") (v "../..") = true
223223+is_prefix (v "..") (v ".") = false
224224+]}
225225+ However, on {{!normalize}normalized}, {{!is_abs}absolute} paths,
226226+ the prefix relation does entail directory containement. See also
227227+ {!is_rooted}. *)
228228+229229+val is_prefix : t -> t -> bool
230230+(** [is_prefix prefix p] is [true] if [prefix] is a prefix of
231231+ [p]. This checks that:
232232+ {ul
233233+ {- [prefix] has the same optional volume as [p].}
234234+ {- [prefix] has the same optional root directory separator as [p].}
235235+ {- The list of segments of [prefix] is a prefix of those of
236236+ [p], ignoring the last empty segment of [prefix] if the number of
237237+ non-empty segments of [p] is strictly larger than those of [prefix].
238238+ This means that [is_prefix (v "a/") (v "a/b")] is [true] but
239239+ [is_prefix (v "a/") (v "a")] is [false]}}
240240+ {{!ex_is_prefix}Examples}. *)
241241+242242+val find_prefix : t -> t -> t option
243243+(** [find_prefix p p'] is [Some prefix] if there exists [prefix] such
244244+ that [prefix] is the longest path with [is_prefix prefix p &&
245245+ is_prefix prefix p' = true] and [None] otherwise. Note that if
246246+ both [p] and [p'] are absolute and have the same volume then a
247247+ prefix always exists: the {{!is_root}root path} of their volume.
248248+ {{!ex_find_prefix}Examples}. *)
249249+250250+val rem_prefix : t -> t -> t option
251251+(** [rem_prefix prefix p] is:
252252+ {ul
253253+ {- [None] if [prefix] is not a {{!is_prefix}prefix} of [p] or if [prefix]
254254+ and [p] are {{!equal}equal}.}
255255+ {- [Some q] otherwise where [q] is [p] without the
256256+ prefix [prefix] and preserves [p]'s
257257+ {{!is_dir_path}directoryness}. This means that [q] is a always
258258+ {{!is_rel}relative} and that the path [prefix // q] and [p] represent the
259259+ same paths. They may however differ syntactically when
260260+ converted to a string.}}
261261+ {{!ex_rem_prefix}Examples}. *)
262262+263263+(** {1:rootrel Roots and relativization} *)
264264+265265+val relativize : root:t -> t -> t option
266266+(** [relativize ~root p] tries to express path [p] relative to the directory
267267+ [root]:
268268+ {ul
269269+ {- [Some q] if there exists a {{!is_rel}relative} path [q] such
270270+ that [root // q] and [p] represent the same paths,
271271+ {{!is_dir_path}directoryness} included. They may however differ
272272+ syntactically when converted to a string. Note that [q] is
273273+ {{!normalize}normalized}.}
274274+ {- [None] otherwise. This notably happens if [p] is absolute
275275+ and [root] relative or if [root] is relative and [p] is absolute.
276276+ In both cases it's not possible to know how to get from [p] from
277277+ [root].}}
278278+ {{!ex_relativize}Examples}. *)
279279+280280+val is_rooted : root:t -> t -> bool
281281+(** [is_rooted root p] is [true] iff the path [p] is the
282282+ {{!is_dir_path}{e directory}} [root] or contained in [root] and that [p]
283283+ can be {{!relativize} relativized} w.r.t. [root] (the normalized relative
284284+ path will have no parent directory segments).
285285+ {{!ex_is_rooted}Examples}. *)
286286+287287+(** {1:predicates Predicates and comparison} *)
288288+289289+val is_rel : t -> bool
290290+(** [is_rel p] is [true] iff [p] is a relative path, i.e. the root
291291+ directory separator is missing in [p]. *)
292292+293293+val is_abs : t -> bool
294294+(** [is_abs p] is [true] iff [p] is an absolute path, i.e. the root
295295+ directory separator is present in [p]. *)
296296+297297+val is_root : t -> bool
298298+(** [is_root p] is [true] iff [p] is a root directory, i.e. [p] has the
299299+ root directory separator and a single, empty, segment.
300300+ {{!ex_is_root}Examples}.
301301+302302+ {b Warning.} By definition this is a syntactic test. For example it will
303303+ return [false] on ["/a/.."] or ["/.."]. {{!normalize}Normalizing}
304304+ the path before testing avoids this problem. *)
305305+306306+val is_current_dir : ?prefix:bool -> t -> bool
307307+(** [is_current_dir p] is true iff [p] is the current relative directory,
308308+ i.e. either ["."] or ["./"]. If [prefix] is [true] (defaults to [false])
309309+ simply checks that [p] is {{!is_rel}relative} and its first segment
310310+ is ["."].
311311+312312+ {b Warning.} By definition this is a syntactic test. For example it will
313313+ return [false] on ["./a/.."] or ["./."]. {{!normalize}Normalizing} the
314314+ path before testing avoids this problem. *)
315315+316316+val is_parent_dir : ?prefix:bool -> t -> bool
317317+(** [is_parent_dir p] is [true] iff [p] is the relative parent directory,
318318+ i.e. either [".."] or ["../"]. If [prefix] is [true] (defaults to [false]),
319319+ simply checks that [p] is {{!is_rel}relative} and its first segment
320320+ is [".."].
321321+322322+ {b Warning.} By definition this is a syntactic test. For example it will
323323+ return [false] on ["./a/../.."] or ["./.."]. {{!normalize}Normalizing} the
324324+ path before testing avoids this problem. *)
325325+326326+val is_dotfile : t -> bool
327327+(** [is_dotfile p] is [true] iff [p]'s {{!basename}basename} is non
328328+ empty and starts with a ['.'].
329329+330330+ {b Warning.} By definition this is a syntactic test. For example it will
331331+ return [false] on [".ssh/."]. {{!normalize}Normalizing} the
332332+ path before testing avoids this problem. *)
333333+334334+val equal : t -> t -> bool
335335+(** [equal p p'] is [true] if [p] and [p'] have the same volume
336336+ are both relative or absolute and have the same segments.
337337+338338+ {b Warning.} By definition this is a syntactic test. For example
339339+ [equal (v "./") (v "a/..")] is [false]. {{!normalize}Normalizing}
340340+ the paths before testing avoids this problem. *)
341341+342342+val compare : t -> t -> int
343343+(** [compare p p'] is a total order on paths compatible with {!equal}. *)
344344+345345+(** {1:conversions Conversions and pretty printing} *)
346346+347347+val to_string : t -> string
348348+(** [to_string p] is the path [p] as a string. The result can
349349+ be safely converted back with {!v}. *)
350350+351351+val of_string : string -> (t, [`Msg of string]) result
352352+(** [of_string s] is the string [s] as a path. The following transformations
353353+ are performed on the string:
354354+ {ul
355355+ {- On Windows any ['/'] occurence is converted to ['\\'] before
356356+ any processing occurs.}
357357+ {- Non-initial empty segments are suppressed;
358358+ ["a//b"] becomes ["a/b"], ["//a////b//"] becomes ["//a/b/"], etc.}
359359+ {- On Windows empty absolute UNC paths are completed to
360360+ their root. For example ["\\\\server\\share"] becomes
361361+ ["\\\\server\\share\\"],
362362+ but incomplete UNC volumes like ["\\\\a"] return [Error].}}
363363+364364+ [Error (`Msg (strf "%S: invalid path" s))] is returned if
365365+ {ul
366366+ {- [s] or the path following the {{!split_volume}volume} is empty ([""]),
367367+ except on Windows UNC paths, see above.}
368368+ {- [s] has null byte (['\x00']).}
369369+ {- On Windows, [s] is an invalid UNC path (e.g. ["\\\\"] or ["\\\\a"])}}
370370+ *)
371371+372372+val pp : Format.formatter -> t -> unit
373373+(** [pp ppf p] prints path [p] on [ppf] using {!to_string}. *)
374374+375375+val dump : Format.formatter -> t -> unit
376376+(** [dump ppf p] prints path [p] on [ppf] using {!String.dump}. *)
377377+378378+(** {1:file_exts File extensions}
379379+380380+ The {e file extension} (resp. {e multiple file extension}) of a
381381+ path segment is the suffix that starts at the last (resp. first)
382382+ occurence of a ['.'] that is preceeded by at least one non ['.']
383383+ character. If there is no such occurence in the segment, the
384384+ extension is empty. With these definitions, ["."], [".."],
385385+ ["..."] and dot files like [".ocamlinit"] or ["..ocamlinit"] have
386386+ no extension, but [".emacs.d"] and ["..emacs.d"] do have one.
387387+388388+ {b Warning.} The following functions act on paths whose
389389+ {{!basename}basename} is non empty and do nothing otherwise.
390390+ {{!normalize}Normalizing} [p] before using the functions ensures
391391+ that the functions do nothing iff [p] cannot be named, see
392392+ {!basename}. *)
393393+394394+type ext = string
395395+(** The type for file extensions. *)
396396+397397+val get_ext : ?multi:bool -> t -> ext
398398+(** [get_ext p] is [p]'s {{!basename}basename} file extension or the
399399+ empty string if there is no extension. If [multi] is [true]
400400+ (defaults to [false]), returns the multiple file
401401+ extension. {{!ex_get_ext}Examples}. *)
402402+403403+val has_ext : ext -> t -> bool
404404+(** [has_ext e p] is [true] iff [get_ext p = e || get_ext ~multi:true p = e].
405405+ If [e] doesn't start with a ['.'] one is prefixed before making
406406+ the test. {{!ex_has_ext}Examples}. *)
407407+408408+val mem_ext : ext list -> t -> bool
409409+(** [mem_ext exts p] is
410410+ [List.mem (get_ext p) exts || List.mem (get_ext ~multi:true p) exts]. *)
411411+412412+val exists_ext : ?multi:bool -> t -> bool
413413+(** [exists_ext ~multi p] is [true] iff [p]'s {{!basename}basename}
414414+ file extension is not empty. If [multi] is [true] (default to
415415+ [false]) returns [true] iff [p] has {e more than one} extension.
416416+ {{!ex_exists_ext}Examples}. *)
417417+418418+val add_ext : ext -> t -> t
419419+(** [add_ext ext p] is [p] with the string [ext] concatenated to [p]'s
420420+ {{!basename}basename}, if non empty. If [ext] doesn't start with a ['.']
421421+ one is prefixed to it before concatenation except if [ext] is
422422+ [""]. {{!ex_add_ext}Examples}.
423423+424424+ @raise Invalid_argument if {!is_seg}[ ext] is [false]. *)
425425+426426+val rem_ext : ?multi:bool -> t -> t
427427+(** [rem_ext p] is [p] with the extension of [p]'s
428428+ {{!basename}basename} removed. If [multi] is [true] (default to
429429+ [false]), the multiple file extension is
430430+ removed. {{!ex_rem_ext}Examples}. *)
431431+432432+val set_ext : ?multi:bool -> ext -> t -> t
433433+(** [set_ext ?multi ext p] is [add_ext ext (rem_ext ?multi p)]. *)
434434+435435+val split_ext : ?multi:bool -> t -> t * ext
436436+(** [split_ext ?multi p] is [(rem_ext ?multi p, get_ext ?multi p)]. If this is
437437+ [(q, ext)] the following invariant holds:
438438+ {ul
439439+ {- [equal p (add_ext q ext)]}} *)
440440+441441+val ( + ) : t -> ext -> t
442442+(** [p + ext] is [add_ext ext p]. Left associative. *)
443443+444444+val ( -+ ) : t -> ext -> t
445445+(** [p -+ ext] is [set_ext ext p]. Left associative. *)
446446+447447+(** {1:sets_maps Path sets and maps} *)
448448+449449+type path = t
450450+451451+type set
452452+(** The type for path sets. Membership is determined according to {!equal}. *)
453453+454454+(** Path sets. *)
455455+module Set : sig
456456+457457+ (** {1 Path sets} *)
458458+459459+ include Set.S with type elt := path
460460+ and type t := set
461461+462462+ type t = set
463463+464464+ val min_elt : set -> path option
465465+ (** Exception safe {!Set.S.min_elt}. *)
466466+467467+ val get_min_elt : set -> path
468468+ (** [get_min_let] is like {!min_elt} but @raise Invalid_argument
469469+ on the empty set. *)
470470+471471+ val max_elt : set -> path option
472472+ (** Exception safe {!Set.S.max_elt}. *)
473473+474474+ val get_max_elt : set -> path
475475+ (** [get_max_elt] is like {!max_elt} but @raise Invalid_argument
476476+ on the empty set. *)
477477+478478+ val choose : set -> path option
479479+ (** Exception safe {!Set.S.choose}. *)
480480+481481+ val get_any_elt : set -> path
482482+ (** [get_any_elt] is like {!choose} but @raise Invalid_argument on the
483483+ empty set. *)
484484+485485+ val find : path -> set -> path option
486486+ (** Exception safe {!Set.S.find}. *)
487487+488488+ val get : path -> set -> path
489489+ (** [get] is like {!Set.S.find} but @raise Invalid_argument if
490490+ [elt] is not in [s]. *)
491491+492492+ val of_list : path list -> set
493493+ (** [of_list ps] is a set from the list [ps]. *)
494494+495495+ val pp : ?sep:(Format.formatter -> unit -> unit) ->
496496+ (Format.formatter -> path -> unit) ->
497497+ Format.formatter -> set -> unit
498498+ (** [pp ~sep pp_elt ppf ps] formats the elements of [ps] on
499499+ [ppf]. Each element is formatted with [pp_elt] and elements are
500500+ separated by [~sep] (defaults to {!Format.pp_print_cut}). If the
501501+ set is empty leaves [ppf] untouched. *)
502502+503503+ val dump : Format.formatter -> set -> unit
504504+ (** [dump ppf ps] prints an unspecified representation of [ps] on
505505+ [ppf]. *)
506506+end
507507+508508+(** Path maps. *)
509509+module Map : sig
510510+511511+ (** {1 Path maps} *)
512512+513513+ include Map.S with type key := t
514514+515515+ val min_binding : 'a t -> (path * 'a) option
516516+ (** Exception safe {!Map.S.min_binding}. *)
517517+518518+ val get_min_binding : 'a t -> (path * 'a)
519519+ (** [get_min_binding] is like {!min_binding} but @raise Invalid_argument
520520+ on the empty map. *)
521521+522522+ val max_binding : 'a t -> (path * 'a) option
523523+ (** Exception safe {!Map.S.max_binding}. *)
524524+525525+ val get_max_binding : 'a t -> string * 'a
526526+ (** [get_min_binding] is like {!max_binding} but @raise Invalid_argument
527527+ on the empty map. *)
528528+529529+ val choose : 'a t -> (path * 'a) option
530530+ (** Exception safe {!Map.S.choose}. *)
531531+532532+ val get_any_binding : 'a t -> (path * 'a)
533533+ (** [get_any_binding] is like {!choose} but @raise Invalid_argument
534534+ on the empty map. *)
535535+536536+ val find : path -> 'a t -> 'a option
537537+ (** Exception safe {!Map.S.find}. *)
538538+539539+ val get : path -> 'a t -> 'a
540540+ (** [get k m] is like {!Map.S.find} but raises [Invalid_argument] if
541541+ [k] is not bound in [m]. *)
542542+543543+ val dom : 'a t -> set
544544+ (** [dom m] is the domain of [m]. *)
545545+546546+ val of_list : (path * 'a) list -> 'a t
547547+ (** [of_list bs] is [List.fold_left (fun m (k, v) -> add k v m) empty
548548+ bs]. *)
549549+550550+ val pp : ?sep:(Format.formatter -> unit -> unit) ->
551551+ (Format.formatter -> path * 'a -> unit) -> Format.formatter ->
552552+ 'a t -> unit
553553+ (** [pp ~sep pp_binding ppf m] formats the bindings of [m] on
554554+ [ppf]. Each binding is formatted with [pp_binding] and
555555+ bindings are separated by [sep] (defaults to
556556+ {!Format.pp_print_cut}). If the map is empty leaves [ppf]
557557+ untouched. *)
558558+559559+ val dump : (Format.formatter -> 'a -> unit) -> Format.formatter ->
560560+ 'a t -> unit
561561+ (** [dump pp_v ppf m] prints an unspecified representation of [m] on
562562+ [ppf] using [pp_v] to print the map codomain elements. *)
563563+end
564564+565565+type +'a map = 'a Map.t
566566+(** The type for maps from paths to values of type ['a]. Paths are compared
567567+ with {!compare}. *)
568568+569569+(** {1:tips Tips}
570570+571571+ {ul
572572+ {- The documentation sometimes talks about {e the last non-empty segment of
573573+ a path}. This usually means that we don't care whether the path
574574+ is a {{!is_file_path}file path} (e.g. ["a"]) or a
575575+ {{!is_dir_path}directory path} (e.g. ["a/"]).}
576576+ {- Windows accepts both ['\\'] and ['/'] as directory separator.
577577+ However [Fpath] on Windows converts ['/'] to ['\\'] on the
578578+ fly. Therefore you should either use ['/'] for defining
579579+ constant paths you inject with {!v} or better, construct them
580580+ directly with {!(/)}. {!to_string} then converts paths to strings
581581+ using the platform's specific directory separator {!dir_sep}.}
582582+ {- Avoid platform specific {{!split_volume}volumes} or hard-coding file
583583+ hierarchy conventions in your constants.}
584584+ {- Do not assume there is a single root path and that it is
585585+ ["/"]. On Windows each {{!split_volume}volume} can have a root path.
586586+ Use {!is_root} on {{!normalize}normalized} paths to detect roots.}
587587+ {- Do not use {!to_string} to construct URIs, {!to_string} uses
588588+ {!dir_sep} to separate segments, on Windows this is ['\\'] which
589589+ is not what URIs expect. Access path segments directly
590590+ with {!segs}; note that you will need to percent encode these.}}
591591+592592+ {1:ex Examples}
593593+594594+ {2:ex_add_seg {!add_seg}}
595595+ {ul
596596+ {- [equal (add_seg (v "/a") "b") (v "/a/b")]}
597597+ {- [equal (add_seg (v "/a/") "b") (v "/a/b")]}
598598+ {- [equal (add_seg (v "/a/b") "") (v "/a/b/")]}
599599+ {- [equal (add_seg (v "/a/b/") "") (v "/a/b/")]}
600600+ {- [equal (add_seg (v "/") "") (v "/")]}
601601+ {- [equal (add_seg (v "/") "a") (v "/a")]}
602602+ {- [equal (add_seg (v ".") "") (v "./")]}
603603+ {- [equal (add_seg (v ".") "a") (v "./a")]}
604604+ {- [equal (add_seg (v "..") "") (v "../")]}
605605+ {- [equal (add_seg (v "..") "a") (v "../a")]}}
606606+607607+ {2:ex_append {!append}}
608608+ {ul
609609+ {- [equal (append (v "/a/b/") (v "e/f")) (v "/a/b/e/f")]}
610610+ {- [equal (append (v "/a/b") (v "e/f")) (v "/a/b/e/f")]}
611611+ {- [equal (append (v "/a/b/") (v "/e/f")) (v "/e/f")]}
612612+ {- [equal (append (v "a/b/") (v "e/f")) (v "a/b/e/f")]}
613613+ {- [equal (append (v "a/b") (v "C:e")) (v "C:e")] (Windows)}}
614614+615615+ {2:ex_segs {!segs}}
616616+ {ul
617617+ {- [segs (v "/a/b/") = [""; "a"; "b"; ""]]}
618618+ {- [segs (v "/a/b") = [""; "a"; "b"]]}
619619+ {- [segs (v "a/b/") = ["a"; "b"; ""]]}
620620+ {- [segs (v "a/b") = ["a"; "b"]]}
621621+ {- [segs (v "a") = ["a"]]}
622622+ {- [segs (v "/") = [""; ""]]}
623623+ {- [segs (v "\\\\.\\dev\\") = ["";""]] (Windows)}
624624+ {- [segs (v "\\\\server\\share\\a") = ["";"a"]] (Windows)}
625625+ {- [segs (v "C:a") = ["a"]] (Windows)}
626626+ {- [segs (v "C:\\a") = ["";"a"]] (Windows)}}
627627+628628+ {2:ex_is_dir_path {!is_dir_path}}
629629+ {ul
630630+ {- [is_dir_path (v ".") = true]}
631631+ {- [is_dir_path (v "..") = true]}
632632+ {- [is_dir_path (v "../") = true]}
633633+ {- [is_dir_path (v "/") = true]}
634634+ {- [is_dir_path (v "/a/b/") = true]}
635635+ {- [is_dir_path (v "/a/b") = false]}
636636+ {- [is_dir_path (v "a/") = true]}
637637+ {- [is_dir_path (v "a") = false]}
638638+ {- [is_dir_path (v "a/.") = true]}
639639+ {- [is_dir_path (v "a/..") = true]}
640640+ {- [is_dir_path (v "a/..b") = false]}
641641+ {- [is_dir_path (v "C:\\") = true] (Windows)}
642642+ {- [is_dir_path (v "C:a") = false] (Windows)}}
643643+644644+ {2:ex_is_file_path {!is_file_path}}
645645+ {ul
646646+ {- [is_file_path (v ".") = false]}
647647+ {- [is_file_path (v "..") = false]}
648648+ {- [is_file_path (v "../") = false]}
649649+ {- [is_file_path (v "/") = false]}
650650+ {- [is_file_path (v "/a/b/") = false]}
651651+ {- [is_file_path (v "/a/b") = true]}
652652+ {- [is_file_path (v "a/") = false]}
653653+ {- [is_file_path (v "a") = true]}
654654+ {- [is_file_path (v "a/.") = false]}
655655+ {- [is_file_path (v "a/..") = false]}
656656+ {- [is_file_path (v "a/..b") = true]}
657657+ {- [is_file_path (v "C:\\") = false] (Windows)}
658658+ {- [is_file_path (v "C:a") = true] (Windows)}}
659659+660660+ {2:ex_to_dir_path {!to_dir_path}}
661661+ {ul
662662+ {- [equal (to_dir_path @@ v ".") (v "./")]}
663663+ {- [equal (to_dir_path @@ v "..") (v "../")]}
664664+ {- [equal (to_dir_path @@ v "../") (v "../")]}
665665+ {- [equal (to_dir_path @@ v "/") (v "/")]}
666666+ {- [equal (to_dir_path @@ v "/a/b/") (v "/a/b/")]}
667667+ {- [equal (to_dir_path @@ v "/a/b") (v "/a/b/")]}
668668+ {- [equal (to_dir_path @@ v "a/") (v "a/")]}
669669+ {- [equal (to_dir_path @@ v "a") (v "a/")]}
670670+ {- [equal (to_dir_path @@ v "a/.") (v "a/./")]}
671671+ {- [equal (to_dir_path @@ v "a/..") (v "a/../")]}
672672+ {- [equal (to_dir_path @@ v "a/..b") (v "a/..b/")]}
673673+ {- [equal (to_dir_path @@ v "\\\\server\\share\\")
674674+ (v "\\\\server\\share\\")]
675675+ (Windows)}
676676+ {- [equal (to_dir_path @@ v "C:a") (v "C:a\\")] (Windows)}
677677+ {- [equal (to_dir_path @@ v "C:\\") (v "C:\\")] (Windows)}}
678678+679679+ {2:ex_filename {!filename}}
680680+ {ul
681681+ {- [filename (v ".") = ""]}
682682+ {- [filename (v "./") = ""]}
683683+ {- [filename (v "..") = ""]}
684684+ {- [filename (v "../") = ""]}
685685+ {- [filename (v "../..") = ""]}
686686+ {- [filename (v "/") = ""]}
687687+ {- [filename (v "/a/b/") = ""]}
688688+ {- [filename (v "/a/b") = "b"]}
689689+ {- [filename (v "a/") = ""]}
690690+ {- [filename (v "a") = "a"]}
691691+ {- [filename (v "a/.") = ""]}
692692+ {- [filename (v "a/..") = ""]}
693693+ {- [filename (v "a/..b") = "..b"]}
694694+ {- [filename (v "C:\\") = ""] (Windows)}
695695+ {- [filename (v "C:a") = "a"] (Windows)}}
696696+697697+ {2:ex_split_base {!split_base}}
698698+ {ul
699699+ {- [(split_base @@ v ".") = (v "./"), (v ".")]}
700700+ {- [(split_base @@ v "./") = (v "./"), (v "./")]}
701701+ {- [(split_base @@ v "..") = (v "./"), (v "..")]}
702702+ {- [(split_base @@ v "../") = (v "./"), (v "../")]}
703703+ {- [(split_base @@ v "../../") = (v "../"), (v "../")]}
704704+ {- [(split_base @@ v ".././") = (v "../"), (v "./")]}
705705+ {- [(split_base @@ v "../../../") = (v "../../"), (v "../")]}
706706+ {- [(split_base @@ v "/") = (v "/"), (v "./")]}
707707+ {- [(split_base @@ v "/a/b/") = (v "/a/"), (v "b/")]}
708708+ {- [(split_base @@ v "/a/b") = (v "/a/"), (v "b")]}
709709+ {- [(split_base @@ v "a/") = (v "./"), (v "a/")]}
710710+ {- [(split_base @@ v "a") = (v "./"), (v "a")]}
711711+ {- [(split_base @@ v "a/b") = (v "a/"), (v "b")]}
712712+ {- [(split_base @@ v "a/b/") = (v "a/b/"), (v "b/")]}
713713+ {- [(split_base @@ v "a/.") = (v "a/"), (v ".")]}
714714+ {- [(split_base @@ v "a/..") = (v "a/"), (v "..")]}
715715+ {- [(split_base @@ v "a/../..") = (v "a/../"), (v "..")]}
716716+ {- [(split_base @@ v "a/..b") = (v "a/"), (v "..b")]}
717717+ {- [(split_base @@ v "./a") = (v "./"), (v "a")]}
718718+ {- [(split_base @@ v "./a/") = (v "./"), (v "a/")]}
719719+ {- [(split_base @@ v "../a") = (v "../"), (v "a")]}
720720+ {- [(split_base @@ v "../a/") = (v "../"), (v "a/")]}}
721721+722722+ {2:ex_basename {!basename}}
723723+ {ul
724724+ {- [basename (v ".") = ""]}
725725+ {- [basename (v "..") = ""]}
726726+ {- [basename (v "../") = ""]}
727727+ {- [basename (v "../../") = ""]}
728728+ {- [basename (v "/") = ""]}
729729+ {- [basename (v "/a/b/") = "b"]}
730730+ {- [basename (v "/a/b") = "b"]}
731731+ {- [basename (v "a/") = "a"]}
732732+ {- [basename (v "a") = "a"]}
733733+ {- [basename (v "a/.") = ""]}
734734+ {- [basename (v "a/./") = ""]}
735735+ {- [basename (v "a/..") = ""]}
736736+ {- [basename (v "a/..b") = "..b"]}
737737+ {- [basename (v "./a") = "a"]}
738738+ {- [basename (v "../a") = "a"]}
739739+ {- [basename (v "C:\\") = ""] (Windows)}
740740+ {- [basename (v "C:a") = "a"] (Windows)}}
741741+742742+ {2:ex_parent {!parent}}
743743+ {ul
744744+ {- [equal (parent @@ v ".") (v "./../")]}
745745+ {- [equal (parent @@ v "..") (v "../../")]}
746746+ {- [equal (parent @@ v "../") (v "../../")]}
747747+ {- [equal (parent @@ v "../../") (v "../../../")]}
748748+ {- [equal (parent @@ v "/") (v "/")]}
749749+ {- [equal (parent @@ v "/a/b/") (v "/a/")]}
750750+ {- [equal (parent @@ v "/a/b") (v "/a/")]}
751751+ {- [equal (parent @@ v "a/") (v "./")]}
752752+ {- [equal (parent @@ v "a") (v "./")]}
753753+ {- [equal (parent @@ v "a/.") (v "a/./../")]}
754754+ {- [equal (parent @@ v "a/./") (v "a/./../")]}
755755+ {- [equal (parent @@ v "a/..") (v "a/../../")]}
756756+ {- [equal (parent @@ v "a/../") (v "a/../../")]}
757757+ {- [equal (parent @@ v "a/..b") (v "a/")]}
758758+ {- [equal (parent @@ v "./a") (v "./")]}
759759+ {- [equal (parent @@ v "../a") (v "../")]}
760760+ {- [equal (parent @@ v "../../a") (v "../../")]}
761761+ {- [equal (parent @@ v "\\\\server\\share\\") (v "\\\\server\\share\\")]
762762+ (Windows)}
763763+ {- [equal (parent @@ v "C:\\") (v "C:\\")] (Windows)}
764764+ {- [equal (parent @@ v "C:a") (v "C:.\\")] (Windows)}}
765765+766766+ {2:ex_rem_empty_seg {!rem_empty_seg}}
767767+ {ul
768768+ {- [equal (rem_empty_seg @@ v ".") (v ".")]}
769769+ {- [equal (rem_empty_seg @@ v "..") (v "..")]}
770770+ {- [equal (rem_empty_seg @@ v "../") (v "..")]}
771771+ {- [equal (rem_empty_seg @@ v "../../") (v "../..")]}
772772+ {- [equal (rem_empty_seg @@ v "/") (v "/")]}
773773+ {- [equal (rem_empty_seg @@ v "/a/b/") (v "/a/b")]}
774774+ {- [equal (rem_empty_seg @@ v "/a/b") (v "/a/b")]}
775775+ {- [equal (rem_empty_seg @@ v "a/") (v "a")]}
776776+ {- [equal (rem_empty_seg @@ v "a") (v "a")]}
777777+ {- [equal (rem_empty_seg @@ v "a/.") (v "a/.")]}
778778+ {- [equal (rem_empty_seg @@ v "a/./") (v "a/.")]}
779779+ {- [equal (rem_empty_seg @@ v "a/..") (v "a/..")]}
780780+ {- [equal (rem_empty_seg @@ v "a/../") (v "a/..")]}
781781+ {- [equal (rem_empty_seg @@ v "a/..b") (v "a/..b")]}
782782+ {- [equal (rem_empty_seg @@ v "./a") (v "./a")]}
783783+ {- [equal (rem_empty_seg @@ v "../a") (v "../a")]}
784784+ {- [equal (rem_empty_seg @@ v "../../a") (v "../../a")]}
785785+ {- [equal (rem_empty_seg @@ v "\\\\server\\share\\")
786786+ (v "\\\\server\\share\\")] (Windows)}
787787+ {- [equal (rem_empty_seg @@ v "C:\\") (v "C:\\")] (Windows)}
788788+ {- [equal (rem_empty_seg @@ v "C:a\\") (v "C:a")] (Windows)}}
789789+790790+ {2:ex_normalize {!normalize}}
791791+ {ul
792792+ {- [equal (normalize @@ v ".") (v "./")]}
793793+ {- [equal (normalize @@ v "..") (v "../")]}
794794+ {- [equal (normalize @@ v "../") (v "../")]}
795795+ {- [equal (normalize @@ v "../../") (v "../../")]}
796796+ {- [equal (normalize @@ v "/") (v "/")]}
797797+ {- [equal (normalize @@ v "/a/b/") (v "/a/b/")]}
798798+ {- [equal (normalize @@ v "/a/b") (v "/a/b")]}
799799+ {- [equal (normalize @@ v "a/") (v "a/")]}
800800+ {- [equal (normalize @@ v "a") (v "a")]}
801801+ {- [equal (normalize @@ v "a/.") (v "a/")]}
802802+ {- [equal (normalize @@ v "a/./") (v "a/")]}
803803+ {- [equal (normalize @@ v "a/..") (v "./")]}
804804+ {- [equal (normalize @@ v "a/../") (v "./")]}
805805+ {- [equal (normalize @@ v "a/..b") (v "a/..b")]}
806806+ {- [equal (normalize @@ v "./a") (v "a")]}
807807+ {- [equal (normalize @@ v "../a") (v "../a")]}
808808+ {- [equal (normalize @@ v "../../a") (v "../../a")]}
809809+ {- [equal (normalize @@ v "./a/..") (v "./")]}
810810+ {- [equal (normalize @@ v "/a/b/./..") (v "/a/")]}
811811+ {- [equal (normalize @@ v "/../..") (v "/")]}
812812+ {- [equal (normalize @@ v "/a/../..") (v "/")]}
813813+ {- [equal (normalize @@ v "./../..") (v "../../")]}
814814+ {- [equal (normalize @@ v "../../a/") (v "../../a/")]}
815815+ {- [equal (normalize @@ v "/a/b/c/./../../g") (v "/a/g")]}
816816+ {- [equal (normalize @@ v "/a/b/c/./../../g/") (v "/a/g/")]}
817817+ {- [equal (normalize @@ v "\\\\?\\UNC\\server\\share\\..")
818818+ (v "\\\\?\\UNC\\server\\share\\")] (Windows)}
819819+ {- [equal (normalize @@ v "\\\\server\\share\\")
820820+ (v "\\\\server\\share\\")] (Windows)}
821821+ {- [equal (normalize @@ v "C:\\") (v "C:\\")] (Windows)}
822822+ {- [equal (normalize @@ v "C:a\\") (v "C:a\\")] (Windows)}}
823823+824824+ {2:ex_is_prefix {!is_prefix}}
825825+ {ul
826826+ {- [is_prefix (v "/a/b") (v "/a/b") = true]}
827827+ {- [is_prefix (v "/a/b") (v "/a/bc") = false]}
828828+ {- [is_prefix (v "/a/b") (v "/a/b/") = true]}
829829+ {- [is_prefix (v "a/b/") (v "a/b") = false]}
830830+ {- [is_prefix (v "a/b/") (v "a/b/") = true]}
831831+ {- [is_prefix (v "a/b/") (v "a/b/c") = true]}
832832+ {- [is_prefix (v ".") (v "./") = true]}
833833+ {- [is_prefix (v "..") (v ".") = false]}
834834+ {- [is_prefix (v "C:a") (v "a") = false] (Windows)}}
835835+836836+ {2:ex_find_prefix {!find_prefix}}
837837+ {ul
838838+ {- [find_prefix (v "a/b/c") (v "a/b/d")] is [Some (v "a/b/")]}
839839+ {- [find_prefix (v "a/b/c") (v "a/b/cd")] is [Some (v "a/b/")]}
840840+ {- [find_prefix (v "a/b") (v "a/b")] is [Some (v "a/b")]}
841841+ {- [find_prefix (v "a/b") (v "a/b/")] is [Some (v "a/b")]}
842842+ {- [find_prefix (v "a/b") (v "e/f")] is [None]}
843843+ {- [find_prefix (v "/a/b") (v "/e/f")] is [Some (v "/")]}
844844+ {- [find_prefix (v "/a/b") (v "e/f")] is [None]}
845845+ {- [find_prefix (v "C:\\a") (v "\\a")] is [None] (Windows)}}
846846+847847+ {2:ex_rem_prefix {!rem_prefix}}
848848+ {ul
849849+ {- [rem_prefix (v "a/b/") (v "a/b")] is [None]}
850850+ {- [rem_prefix (v "a/b/") (v "a/b/")] is [None]}
851851+ {- [rem_prefix (v "a/b") (v "a/b")] is [None]}
852852+ {- [rem_prefix (v "a/b") (v "a/b/")] is [Some "./"]}
853853+ {- [rem_prefix (v "a/b") (v "a/b/c")] is [Some (v "c")]}
854854+ {- [rem_prefix (v "a/b/") (v "a/b/c")] is [Some (v "c")]}
855855+ {- [rem_prefix (v "a/b") (v "a/b/c/")] is [Some (v "c/")]}
856856+ {- [rem_prefix (v "a/b/") (v "a/b/c/")] is [Some (v "c/")]}
857857+ {- [rem_prefix (v "C:\\a") (v "C:\\a\\b")] is [Some (v "b")] (Windows)}}
858858+859859+ {2:ex_relativize {!relativize}}
860860+ {ul
861861+ {- [relativize ~root:(v "/a/b") (v "/a/b")] is [Some (v ".")]}
862862+ {- [relativize ~root:(v "/a/b") (v "/a/b/")] is [Some (v "./")]}
863863+ {- [relativize ~root:(v "/a/b/") (v "/a/b/")] is [Some (v "./")]}
864864+ {- [relativize ~root:(v "/a/b/") (v "/a/b")] is [Some (v "../b")]}
865865+ {- [relativize ~root:(v "a/b") (v "a/b")] is [Some (v ".")]}
866866+ {- [relativize ~root:(v "a/b/") (v "a/b/")] is [Some (v "./")]}
867867+ {- [relativize ~root:(v "a/b") (v "a/b/")] is [Some (v "./")]}
868868+ {- [relativize ~root:(v "a/b/") (v "a/b")] is [Some (v "../b")]}
869869+ {- [relativize ~root:(v "/a/b") (v "c")] is [None]}
870870+ {- [relativize ~root:(v "/a/b") (v "/c")] is [Some (v "../../c")]}
871871+ {- [relativize ~root:(v "/a/b") (v "/c/")] is [Some (v "../../c/")]}
872872+ {- [relativize ~root:(v "/a/b") (v "/c")] is [Some (v "../../c")]}
873873+ {- [relativize ~root:(v "/a/b") (v "/c/")] is [Some (v "../../c/")]}
874874+ {- [relativize ~root:(v "/a/b") (v "/a/b/c")] is [Some (v "c")]}
875875+ {- [relativize ~root:(v "/a/b") (v "/a/b/c/")] is [Some (v "c/")]}
876876+ {- [relativize ~root:(v "a/b") (v "/c")] is [None].}
877877+ {- [relativize ~root:(v "a/b") (v "c")] is [Some (v "../../c")]}
878878+ {- [relativize ~root:(v "a/b") (v "c/")] is [Some (v "../../c/")]}
879879+ {- [relativize ~root:(v "a/b") (v "a/b/c")] is [Some (v "c")]}
880880+ {- [relativize ~root:(v "a/b") (v "a/b")] is [Some (v ".")]}
881881+ {- [relativize ~root:(v "a/b") (v "a/b/")] is [Some (v ".")]}
882882+ {- [relativize ~root:(v "../") (v "./")] is [None]}
883883+ {- [relativize ~root:(v "../a") (v "b")] is [None]}
884884+ {- [relativize ~root:(v "../a") (v "../b/c")] is [Some (v "../b/c")]}
885885+ {- [relativize ~root:(v "../../a") (v "../b")] is [None]}
886886+ {- [relativize ~root:(v "../a") (v "../../b")] is [(Some "../../b")]}}
887887+888888+ {2:ex_is_rooted {!is_rooted}}
889889+ {ul
890890+ {- [is_rooted ~root:(v "a/b") (v "a/b") = false]}
891891+ {- [is_rooted ~root:(v "a/b") (v "a/b/") = true]}
892892+ {- [is_rooted ~root:(v "a/b/") (v "a/b") = false]}
893893+ {- [is_rooted ~root:(v "a/b/") (v "a/b/") = true]}
894894+ {- [is_rooted ~root:(v "./") (v "a") = true]}
895895+ {- [is_rooted ~root:(v "./") (v "a/") = true]}
896896+ {- [is_rooted ~root:(v "./") (v "a/../") = true]}
897897+ {- [is_rooted ~root:(v "./") (v "..") = false]}
898898+ {- [is_rooted ~root:(v "../") (v "./") = false]}
899899+ {- [is_rooted ~root:(v "../") (v "a") = false]}
900900+ {- [is_rooted ~root:(v "../") (v "../") = true]}
901901+ {- [is_rooted ~root:(v "../") (v "../a") = true]}
902902+ {- [is_rooted ~root:(v "../a") (v "./") = false]}
903903+ {- [is_rooted ~root:(v "/a") (v "/a/..") = true]}
904904+ {- [is_rooted ~root:(v "/a") (v "/a/../") = true]}
905905+ {- [is_rooted ~root:(v "/a") (v "/..") = true]}}
906906+907907+ {2:ex_is_root {!is_root}}
908908+ {ul
909909+ {- [is_root (v "/") = true]}
910910+ {- [is_root (v "/a") = false]}
911911+ {- [is_root (v "/a/..") = false]}
912912+ {- [is_root (v "//") = true] (POSIX)}
913913+ {- [is_root (v "\\\\.\\dev\\") = true] (Windows)}
914914+ {- [is_root (v "\\\\.\\dev\\a") = false] (Windows)}
915915+ {- [is_root (v "\\\\server\\share\\") = true] (Windows)}
916916+ {- [is_root (v "\\\\server\\share\\a") = false] (Windows)}
917917+ {- [is_root (v "C:\\") = true] (Windows)}
918918+ {- [is_root (v "C:a") = false] (Windows)}
919919+ {- [is_root (v "C:\\a") = false] (Windows)}}
920920+921921+ {2:ex_get_ext {!get_ext}}
922922+ {ul
923923+ {- [get_ext (v "/") = ""]}
924924+ {- [get_ext (v "a/b") = ""]}
925925+ {- [get_ext (v "a/b.mli/..") = ""]}
926926+ {- [get_ext (v "a/b.mli/...") = ""]}
927927+ {- [get_ext (v "a/b.") = "."]}
928928+ {- [get_ext (v "a/b.mli") = ".mli"]}
929929+ {- [get_ext ~multi:true (v "a/b.mli") = ".mli"]}
930930+ {- [get_ext (v "a/b.mli/") = ".mli"]}
931931+ {- [get_ext (v "a/.ocamlinit") = ""]}
932932+ {- [get_ext (v "a/.emacs.d") = ".d"]}
933933+ {- [get_ext (v "a/.emacs.d/") = ".d"]}
934934+ {- [get_ext ~multi:true (v "a/.emacs.d") = ".d"]}
935935+ {- [get_ext (v "a.tar.gz") = ".gz"]}
936936+ {- [get_ext ~multi:true (v "a.tar.gz") = ".tar.gz"]}}
937937+938938+ {2:ex_has_ext {!has_ext}}
939939+ {ul
940940+ {- [has_ext "mli" (v "a/b.mli") = true]}
941941+ {- [has_ext ".mli" (v "a/b.mli") = true]}
942942+ {- [has_ext ".mli" (v "a/b.mli/") = true]}
943943+ {- [has_ext ".mli" (v "a/bmli") = false]}
944944+ {- [has_ext "mli" (v "a/bmli") = false]}
945945+ {- [has_ext ".tar.gz" (v "a/f.tar.gz") = true]}
946946+ {- [has_ext "tar.gz" (v "a/f.tar.gz") = true]}
947947+ {- [has_ext ".gz" (v "a/f.tar.gz") = true]}
948948+ {- [has_ext ".tar" (v "a/f.tar.gz") = false]}
949949+ {- [has_ext ".cache" (v "a/.cache") = false]}
950950+ {- [has_ext "" (v "a/b") = false]}
951951+ {- [has_ext "" (v "a/b.") = true]}
952952+ {- [has_ext "." (v "a/b.") = true]}}
953953+954954+ {2:ex_exists_ext {!exists_ext}}
955955+ {ul
956956+ {- [exists_ext (v "a/f") = false]}
957957+ {- [exists_ext (v "a/f.") = true]}
958958+ {- [exists_ext (v "a/f.gz") = true]}
959959+ {- [exists_ext ~multi:true (v "a/f.gz") = false]}
960960+ {- [exists_ext (v "a/f.tar.gz") = true]}
961961+ {- [exists_ext ~multi:true (v "a/f.tar.gz") = true]}
962962+ {- [exists_ext (v "a/f.tar.gz/") = true]}
963963+ {- [exists_ext (v ".emacs.d") = true]}
964964+ {- [exists_ext (v ".emacs.d/") = true]}
965965+ {- [exists_ext (v ".ocamlinit") = false]}}
966966+967967+ {2:ex_add_ext {!add_ext}}
968968+ {ul
969969+ {- [equal (add_ext "mli" (v "a/b")) (v "a/b.mli")]}
970970+ {- [equal (add_ext ".mli" (v "a/b")) (v "a/b.mli")]}
971971+ {- [equal (add_ext ".mli" (v "a/b/")) (v "a/b.mli/")]}
972972+ {- [equal (add_ext ".mli" (v "/")) (v "/")]}
973973+ {- [equal (add_ext ".mli" (v "a/b/..")) (v "a/b/..")]}
974974+ {- [equal (add_ext "." (v "a/b")) (v "a/b.")]}
975975+ {- [equal (add_ext "" (v "a/b")) (v "a/b")]}
976976+ {- [equal (add_ext "tar.gz" (v "a/f")) (v "a/f.tar.gz")]}
977977+ {- [equal (add_ext ".tar.gz" (v "a/f")) (v "a/f.tar.gz")]}
978978+ {- [equal (add_ext "gz" (v "a/f.tar") ) (v "a/f.tar.gz")]}
979979+ {- [equal (add_ext ".gz" (v "a/f.tar") ) (v "a/f.tar.gz")]}}
980980+981981+ {2:ex_rem_ext {!rem_ext}}
982982+ {ul
983983+ {- [equal (rem_ext @@ v "/") (v "/")]}
984984+ {- [equal (rem_ext @@ v "/a/b") (v "/a/b")]}
985985+ {- [equal (rem_ext @@ v "/a/b.mli") (v "/a/b")]}
986986+ {- [equal (rem_ext @@ v "/a/b.mli/") (v "/a/b/")]}
987987+ {- [equal (rem_ext @@ v "/a/b.mli/..") (v "/a/b.mli/..")]}
988988+ {- [equal (rem_ext @@ v "/a/b.mli/.") (v "/a/b.mli/.")]}
989989+ {- [equal (rem_ext @@ v "a/.ocamlinit") (v "a/.ocamlinit")]}
990990+ {- [equal (rem_ext @@ v "a/.emacs.d") (v "a/.emacs")]}
991991+ {- [equal (rem_ext @@ v "a/.emacs.d/") (v "a/.emacs/")]}
992992+ {- [equal (rem_ext @@ v "f.tar.gz") (v "f.tar")]}
993993+ {- [equal (rem_ext ~multi:true @@ v "f.tar.gz") (v "f")]}
994994+ {- [equal (rem_ext ~multi:true @@ v "f.tar.gz/") (v "f/")]}} *)