···540540 let rel = List.fold_left (fun acc _ -> dotdot :: acc) [p] root in
541541 (Some (String.concat ~sep:dir_sep rel))
542542543543+let relativize ~root p =
544544+ let root = (* root is always interpreted as a directory *)
545545+ let root = normalize root in
546546+ if root.[String.length root - 1] = dir_sep_char then root else
547547+ root ^ dir_sep
548548+ in
549549+ let p = normalize p in
550550+ let rec walk root p = match root, p with
551551+ | (".." :: _, s :: _) when s <> ".." ->
552552+ (* [root] has too many up segments. Cannot walk down to express [p],
553553+ e.g. "../a" can't be expressed relative to "../../". *)
554554+ None
555555+ | (sr :: root, sp :: (_ :: _ as p)) when sr = sp ->
556556+ (* the next directory in [root] and [p] match and it's not the last
557557+ segment of [p], walk to next segment *)
558558+ walk root p
559559+ | [""], [""] ->
560560+ (* walk ends at the end of both path simultaneously, [p] is a
561561+ directory that matches exactly [root] expressed as a directory. *)
562562+ Some ["."; ""]
563563+ | root, p ->
564564+ (* walk ends here, either the next directory is different in
565565+ [root] and [p] or it is equal but it is the last one for [p]
566566+ and different from [""] (i.e. [p] is a file path and prefix
567567+ of [root]). To get to the current position from the remaining
568568+ root we need to go up the number of non-empty segments that
569569+ remain in [root] (length root - 1). To get to the path [p]
570570+ from the current position we just use [p] so prepending
571571+ length root - 1 .. segments to [p] tells us how to go from
572572+ the remaining root to [p]. *)
573573+ Some (List.fold_left (fun acc _ -> dotdot :: acc) p (List.tl root))
574574+ in
575575+ match segs root, segs p with
576576+ | ("" :: _, s :: _)
577577+ | (s :: _, "" :: _) when s <> "" ->
578578+ (* absolute/relative mismatch *)
579579+ None
580580+ | ["."; ""], p ->
581581+ (* p is relative and expressed w.r.t. "./", so it is itself. *)
582582+ Some p
583583+ | root, p ->
584584+ (* walk in the segments of root and p until a segment mismatches.
585585+ at that point express the remaining p relative to the remaining
586586+ root. Note that because of normalization both [root] and [p] may
587587+ only have initial .. segments and [root] by construction has a
588588+ final "" segment. *)
589589+ walk root p
590590+591591+let relativize ~root p = match relativize ~root p with
592592+| None -> None
593593+| Some segs -> Some (String.concat ~sep:dir_sep segs)
594594+543595(* Predicates and comparison *)
544596545597let is_rel_posix p = p.[0] <> dir_sep_char
+75-25
src/fpath.mli
···227227228228(** {1 Roots and relativization} *)
229229230230-val rooted : root:t -> t -> t option
231231-(** [rooted ~root p] is:
230230+val relativize : root:t -> t -> t option
231231+(** [relativize ~root p] is:
232232+ {ul
233233+ {- [Some q] if there exists a {{!is_relative}relative} path [q] such
234234+ that [root // q] and [p] represent the same paths,
235235+ {{!is_dir_path}directoryness} included (they may however differ
236236+ syntactically when converted to a string).}
237237+ {- [None] otherwise}}
238238+239239+ {{!ex_relativize}Examples.} *)
240240+241241+(*
242242+243243+val is_rooted : root:t -> t -> bool
244244+(** [is_rooted root p] is [true] iff [p] is equal or contained in the
245245+ directory represented by [root] (if [root] is a {{!is_file_path}file path},
246246+ the path {!to_dir_path}[ root] is used instead).
247247+ {{!ex_is_rooted}Examples.} *)
248248+249249+val rooted_append : ?normalized:bool -> root:t -> t -> t option
250250+(** [rooted_append ~root p] {{!appends}appends} [p] to [root] and
251251+ returns a result iff [is_rooted root (append root t)] is [true].
252252+ If [normalized] is [true] the result is normalized.
253253+ {{!ex_rooted_append}Examples.} *)
254254+*)
255255+256256+(*
257257+258258+259259+ the path [p] is contained in path [root].
232260 {ul
233261 {- [None] if [prefix]
234262 [is_prefix (normalize root) (normalize @@ append root p) = false].}
···236264 In other words it ensures that an absolute path [p] or a relative
237265 path [p] expressed w.r.t. [root] expresses a path that is
238266 within the [root] file hierarchy. {{!ex_rooted}Examples}. *)
239239-240240-val relativize : root:t -> t -> t option
241241-(** [relativize ~root p] expresses [p] relative to [root] without
242242- consulting the file system. This is:
243243- {ul
244244- {- [None] if [find_prefix (normalize root) (normalize p)] is [None] or
245245- if the number of initial relative [..] segments is larger in
246246- [(normalize root)] than in [normalize p] (intuitively you can't
247247- come back from [root] to [p] without knowing the absolute path to
248248- the current working directory).}
249249- {- [Some q] otherwise with [q] such that
250250- [equal (normalize (append root q)) (normalize p) = true].}}
251251- {{!ex_relativize}Examples.} *)
252267253268(** {1:predicates Predicates and comparison} *)
254269···276291 {b Warning.} By definition this is a syntactic test. For example it will
277292 return [false] on ["./a/.."] or ["./."]. {{!normalize}Normalizing} the
278293 path before testing avoids this problem. *)
294294+295295+(*
296296+val is_parent_dir : t -> bool
297297+(** [is_parent_dir p] is [true] iff [p] is the relative parent directory,
298298+ i.e. either [".."] or ["../"]. {{!ex_is_parent_dir}Examples}.
299299+300300+ {b Warning.} By definition this is a syntactic test. For example it will
301301+ return [false] on ["./a/../.."] or ["./.."]. {{!normalize}Normalizing} the
302302+ path before testing avoids this problem. *)
303303+*)
279304280305val is_dotfile : t -> bool
281306(** [is_dotfile p] is [true] iff [p]'s last non-empty segment is not
···802827 {- [rem_prefix (v "a/b/") (v "a/b/c/")] is [Some (v "c/")]}
803828 {- [rem_prefix (v "C:\\a") (v "C:\\a\\b")] is [Some (v "b")] (Windows)}}
804829830830+ {2:ex_is_rooted {!is_rooted}}
831831+ {ul
832832+ {- [is_rooted ~root:(v "a/b") (v "a/b") = true]}
833833+ {- [is_rooted ~root:(v "a/b/") (v "a/b") = true]}
834834+ {- [is_rooted ~root:(v "a/b") (v "a/b/") = true]}
835835+ {- [is_rooted ~root:(v "a/b/") (v "a/b/") = true]}
836836+ {- [is_rooted ~root:(v "./") (v "a") = true]}
837837+ {- [is_rooted ~root:(v "./") (v "a/") = true]}
838838+ {- [is_rooted ~root:(v "./") (v "a/../") = true]}
839839+ {- [is_rooted ~root:(v "./") (v "../") = false]}
840840+ {- [is_rooted ~root:(v "../") (v "./") = true]}
841841+ {- [is_rooted ~root:(v "../a") (v "./") = false]}
842842+ {- [is_rooted (v "/a/b") (v "/a/b/c")] is [Some (v "/a/b/c")]}
843843+ {- [is_rooted (v "/a/b") (v "/a/b/c/")] is [Some (v "/a/b/c")]}
844844+ {- [is_rooted (v "/a/b") (v "/a/b/c/.")] is [Some (v "/a/b/c")]}
845845+ {- [is_rooted (v "/a/b") (v "../c")] is [None]}
846846+ {- [is_rooted (v "a/b") (v "c")] is [Some (v "a/b/c")]}
847847+ {- [is_rooted (v "a/b") (v "/c")] is [None]}
848848+ {- [is_rooted (v "a/b") (v "../c")] is [None]}
849849+ {- [is_rooted (v "a/b") (v "c/..")] is [Some (v "a/b")]}
850850+ {- [is_rooted (v "a/b") (v "c/../..")] is [None]}}
851851+805852 {2:ex_rooted {!rooted}}
806853 {ul
807854 {- [rooted (v "/a/b") (v "c")] is [Some (v "/a/b/c")]}
···817864818865 {2:ex_relativize {!relativize}}
819866 {ul
820820- {- [relativize (v "/a/b") (v "c")] is [None]}
821821- {- [relativize (v "/a/b") (v "/c")] is [Some (v "../../c")]}
822822- {- [relativize (v "/a/b") (v "/c/")] is [Some (v "../../c")]}
823823- {- [relativize (v "/a/b") (v "/a/b/c")] is [Some (v "c")]}
824824- {- [relativize (v "/a/b") (v "/a/b")] is [Some (v ".")]}
825825- {- [relativize (v "/a/b") (v "/a/b/")] is [Some (v ".")]}
826826- {- [relativize (v "a/b") (v "/c")] is [None].}
827827- {- [relativize (v "a/b") (v "c")] is [Some (v "../../c")]}
828828- {- [relativize (v "a/b") (v "c/")] is [Some (v "../../c")]}
829829- {- [relativize (v "a/b") (v "a/b/c")] is [Some (v "c")]}
867867+ {- [relativize ~root:(v "/a/b") (v "c")] is [None]}
868868+ {- [relativize ~root:(v "/a/b") (v "/c")] is [Some (v "../../c")]}
869869+ {- [relativize ~root:(v "/a/b") (v "/c/")] is [Some (v "../../c/")]}
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 "/a/b/c")] is [Some (v "c")]}
873873+ {- [relativize ~root:(v "/a/b") (v "/a/b/c/")] is [Some (v "c/")]}
874874+ {- [relativize ~root:(v "/a/b") (v "/a/b")] is [None]}
875875+ {- [relativize ~root:(v "/a/b") (v "/a/b/")] is [Some (v ".")]}
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")]}
830880 {- [relativize (v "a/b") (v "a/b")] is [Some (v ".")]}
831881 {- [relativize (v "a/b") (v "a/b/")] is [Some (v ".")]}
832882 {- [relativize (v "../a") (v "b")] is [None]}
+41-17
test/test_path.ml
···654654 end;
655655 ()
656656657657+657658let rooted = test "Fpath.rooted" @@ fun () ->
659659+(*
658660 let eq = eq_option ~eq:Fpath.equal ~pp:Fpath.pp in
659661 eq (Fpath.rooted (v "/a/b") (v "c")) (Some (v "/a/b/c"));
660662 eq (Fpath.rooted (v "/a/b") (v "/a/b/c")) (Some (v "/a/b/c"));
···671673 eq (Fpath.rooted (v "../../a") (v "a/..")) (Some (v "../../a/"));
672674 eq (Fpath.rooted (v "../../a") (v "../../b")) None;
673675 eq (Fpath.rooted (v "../../a") (v "../../a")) (None);
676676+*)
674677 ()
675678676679let relativize = test "Fpath.relativize" @@ fun () ->
677680 let eq_opt = eq_option ~eq:Fpath.equal ~pp:Fpath.pp in
678681 let relativize root p result = match Fpath.relativize root p with
679682 | None -> eq_opt None result
680680- | Some rp as r ->
683683+ | Some rel as r ->
681684 eq_opt r result;
682682- eqp (Fpath.normalize (Fpath.append root rp)) (Fpath.normalize p);
685685+ eqp (Fpath.normalize (Fpath.append root rel)) (Fpath.normalize p);
683686 in
684684-(*
685685- relativize (v "/a/b") (v "c") None;
686686- relativize (v "/a/b") (v "/c") (Some (v "../../c"));
687687- relativize (v "/a/b") (v "/c/") (Some (v "../../c/"));
688688-(* relativize (v "/a/b") (v "/a/b/c") (Some (v "c")); *)
689689-(* relativize (v "/a/b") (v "/a/b") (Some (v "./"));
690690- relativize (v "/a/b") (v "/a/b/") (Some (v "./")); *)
687687+ relativize (v "/a/") (v "/a") (Some (v "../a"));
688688+ relativize (v "/a/") (v "/a/") (Some (v "./"));
689689+ relativize (v "/a/") (v "/") (Some (v "../"));
690690+ relativize (v "/a/") (v "/../") (Some (v "../"));
691691+ relativize (v "/a/") (v "/../c/d") (Some (v "../c/d"));
692692+ relativize (v "/a/") (v "/../c/d/") (Some (v "../c/d/"));
693693+ relativize (v "/") (v "/../c/d/") (Some (v "c/d/"));
694694+ relativize (v "/") (v "/../c/d") (Some (v "c/d"));
695695+ relativize (v "/") (v "/") (Some (v "./"));
696696+ relativize (v "/") (v "/a") (Some (v "a"));
697697+ relativize (v "/") (v "/a/../b") (Some (v "b"));
698698+ relativize (v "/") (v "/a/../b/") (Some (v "b/"));
699699+ relativize (v "/a/b/") (v "c") None;
700700+ relativize (v "/a/b/") (v "./") None;
701701+ relativize (v "/a/b/") (v "../") None;
702702+ relativize (v "/a/b/") (v "/c") (Some (v "../../c"));
703703+ relativize (v "/a/b/") (v "/c/") (Some (v "../../c/"));
704704+ relativize (v "/a/b/") (v "/c/d/e") (Some (v "../../c/d/e"));
705705+ relativize (v "/a/b/") (v "/c/d/e/../../f") (Some (v "../../c/f"));
706706+ relativize (v "/a/b/") (v "/c/d/e/../../f/") (Some (v "../../c/f/"));
707707+ relativize (v "/a/b/") (v "/./c/d/e/../../f/") (Some (v "../../c/f/"));
708708+ relativize (v "/a/b/") (v "/a/b/c") (Some (v "c"));
709709+ relativize (v "/a/b/") (v "/a/b") (Some (v "../b"));
710710+ relativize (v "/a/b/") (v "/a/b/") (Some (v "./"));
691711 relativize (v "/a/b/c") (v "/d/e/f") (Some (v "../../../d/e/f"));
692712 relativize (v "/a/b/c") (v "/a/b/d") (Some (v "../d"));
693713 relativize (v "a/b") (v "/c") None;
694694-(*
695714 relativize (v "a/b") (v "c") (Some (v "../../c"));
696696-*)
697697-(* relativize (v "a/b") (v "c/") (Some (v "../../c")); *)
715715+ relativize (v "a/b") (v "../c") (Some (v "../../../c"));
716716+ relativize (v "a/b") (v "../c/") (Some (v "../../../c/"));
717717+ relativize (v "a/b") (v "c/") (Some (v "../../c/"));
698718 relativize (v "a/b") (v "a/b/c") (Some (v "c"));
699699-(* relativize (v "a/b") (v "a/b") (Some (v ".")); *)
700700- relativize (v "a/b") (v "a/b/") (Some (v "."));
719719+ relativize (v "a/b") (v "a") (Some (v "../../a"));
720720+ relativize (v "a/b") (v "b") (Some (v "../../b"));
721721+ relativize (v "a/b") (v "c") (Some (v "../../c"));
722722+ relativize (v "a/b/c/") (v "a/d") (Some (v "../../d"));
723723+ relativize (v "a/b/c/") (v "a/b") (Some (v "../../b"));
724724+ relativize (v "a/b/c/") (v "a/b/../../../") (Some (v "../../../../"));
725725+ relativize (v "a/b/c/") (v "a/b/../../../a") (Some (v "../../../../a"));
726726+ relativize (v "a/b") (v "a/b/") (Some (v "./"));
701727 relativize (v "../a") (v "b") None;
702728 relativize (v "../../a") (v "../b") None;
703729 relativize (v "../a") (v "../../b") (Some (v "../../b"));
704704-(* relativize (v "a") (v "../../b") (Some (v "../../../b"));
730730+ relativize (v "a") (v "../../b") (Some (v "../../../b"));
705731 relativize (v "a/c") (v "../../b") (Some (v "../../../../b"));
706706-*)
707707-*)
708732 ()
709733710734let is_abs_rel = test "Fpath.is_abs_rel" @@ fun () ->