diff --git a/.vscode/settings.json b/.vscode/settings.json index 0a76c87..59bc08e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "Fpath", + "Fsegment", "janestreet" ] } \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 3ef426a..e9f91fe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,18 @@ +## 0.2.2 (2024-10-15) + +### Added + +- Added `rem_empty_seg` to `Absolute_path` and `Relative_path`. +- In `fpath-sexp0` export hash keys interfaces compatible with stdlib hash functors. + +### Changed + +- Rename `Fpart` to `Fsegment` to fit `Fpath` terminology. + +### Fixed + +- Fix occurrence of hard coded dir separator. + ## 0.2.1 (2024-09-22) ### Changed diff --git a/README.md b/README.md index 056dbc1..fe6756d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This repository defines 2 OCaml packages named `fpath-sexplib0` and `fpath-base` ## fpath-sexplib0 -This package only depends on `fpath` and `sexplib0`. It defines a single module, `Fpath_sexplib0`, which is designed to be opened to shadow the `Fpath` module. This introduces three new modules to the scope: `Fpart`, `Absolute_path` and `Relative_path`. +This package only depends on `fpath` and `sexplib0`. It defines a single module, `Fpath_sexplib0`, which is designed to be opened to shadow the `Fpath` module. This introduces three new modules to the scope: `Fsegment`, `Absolute_path` and `Relative_path`. **Fpath** is shadowed and retains all its original functionality, with the addition of a sexp serializer: @@ -27,7 +27,7 @@ val classify -> [ `Absolute of Absolute_path.t | `Relative of Relative_path.t ] ``` -**Fpart** is a helper module for representing and manipulating the elements that constitute the '/' separated segments of a path. +**Fsegment** is a helper module for representing and manipulating the elements that constitute the '/' separated segments of a path. **Absolute_path** and **Relative_path** are helper modules that distinguish between classes of paths in the type system, enhancing type safety for applications manipulating paths. Both types are defined as `private Fpath.t`, making it easy to cast and convert paths. @@ -35,7 +35,7 @@ val classify This package further extends `fpath-sexplib0` and adds a dependency on `base`. It is designed to be compatible with `Base`-style containers such as `Map`, `Set`, `Hashtbl`, `Hash_set`. -This package defines a single module, `Fpath_base`, which is designed to be opened to shadow and further extend the four modules from `fpath-sexplib0`: `Fpath`, `Fpart`, `Absolute_path` and `Relative_path`. It exports `hashable` and `comparable` interfaces. +This package defines a single module, `Fpath_base`, which is designed to be opened to shadow and further extend the four modules from `fpath-sexplib0`: `Fpath`, `Fsegment`, `Absolute_path` and `Relative_path`. It exports `hashable` and `comparable` interfaces. ```ocaml module Fpath : sig diff --git a/dune-project b/dune-project index 7112115..4e089f8 100644 --- a/dune-project +++ b/dune-project @@ -33,7 +33,7 @@ (package (name fpath-sexp0) (synopsis - "Adds Fpath.sexp_of_t and defines 3 new modules: Fpart, Absolute_path and Relative_path") + "Adds Fpath.sexp_of_t and defines 3 new modules: Fsegment, Absolute_path and Relative_path") (depends (ocaml (>= 5.2)) diff --git a/fpath-sexp0.opam b/fpath-sexp0.opam index 06ab89a..d284851 100644 --- a/fpath-sexp0.opam +++ b/fpath-sexp0.opam @@ -1,7 +1,7 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: - "Adds Fpath.sexp_of_t and defines 3 new modules: Fpart, Absolute_path and Relative_path" + "Adds Fpath.sexp_of_t and defines 3 new modules: Fsegment, Absolute_path and Relative_path" maintainer: ["Mathieu Barbin "] authors: ["Mathieu Barbin"] license: "MIT" diff --git a/lib/fpath_base/src/fpath_base.ml b/lib/fpath_base/src/fpath_base.ml index f1207e2..44ced75 100644 --- a/lib/fpath_base/src/fpath_base.ml +++ b/lib/fpath_base/src/fpath_base.ml @@ -3,7 +3,6 @@ module Fpath = struct include T include Comparable.Make (T) - let hash t = String.hash (T.to_string t) let hash_fold_t state t = String.hash_fold_t state (T.to_string t) end @@ -12,7 +11,6 @@ module Absolute_path = struct include T include Comparable.Make (T) - let hash t = String.hash (T.to_string t) let hash_fold_t state t = String.hash_fold_t state (T.to_string t) end @@ -21,15 +19,15 @@ module Relative_path = struct include T include Comparable.Make (T) - let hash t = String.hash (T.to_string t) let hash_fold_t state t = String.hash_fold_t state (T.to_string t) end -module Fpart = struct - module T = Fpath_sexp0.Fpart +module Fsegment = struct + module T = Fpath_sexp0.Fsegment include T include Comparable.Make (T) - let hash t = String.hash (T.to_string t) let hash_fold_t state t = String.hash_fold_t state (T.to_string t) end + +module Fpart = Fsegment diff --git a/lib/fpath_base/src/fpath_base.mli b/lib/fpath_base/src/fpath_base.mli index 40c7c00..d628818 100644 --- a/lib/fpath_base/src/fpath_base.mli +++ b/lib/fpath_base/src/fpath_base.mli @@ -28,12 +28,16 @@ module Relative_path : sig val hash_fold_t : Hash.state -> t -> Hash.state end -module Fpart : sig - type t = Fpath_sexp0.Fpart.t +module Fsegment : sig + type t = Fpath_sexp0.Fsegment.t - include module type of Fpath_sexp0.Fpart with type t := t + include module type of Fpath_sexp0.Fsegment with type t := t include Comparable.S with type t := t val hash : t -> int val hash_fold_t : Hash.state -> t -> Hash.state end + +(** This alias is kept for backward compatibility for now but will soon be + deprecated. Please upgrade code to [Fsegment]. *) +module Fpart = Fsegment diff --git a/lib/fpath_base/test/test__absolute_path.ml b/lib/fpath_base/test/test__absolute_path.ml index 64b85a5..7f048ed 100644 --- a/lib/fpath_base/test/test__absolute_path.ml +++ b/lib/fpath_base/test/test__absolute_path.ml @@ -88,11 +88,11 @@ let%expect_test "append" = let%expect_test "extend" = let abs = Absolute_path.v in - let file str = str |> Fpart.v in + let file str = str |> Fsegment.v in let test a b = print_s [%sexp (Absolute_path.extend a b : Absolute_path.t)] in - require_does_raise [%here] (fun () : Fpart.t -> file "a/b"); - [%expect {| (Invalid_argument "a/b: invalid file part") |}]; - require_does_not_raise [%here] (fun () -> ignore (file ".." : Fpart.t)); + require_does_raise [%here] (fun () : Fsegment.t -> file "a/b"); + [%expect {| (Invalid_argument "a/b: invalid file segment") |}]; + require_does_not_raise [%here] (fun () -> ignore (file ".." : Fsegment.t)); [%expect {||}]; test (abs "/") (file "a"); [%expect {| /a |}]; @@ -272,6 +272,31 @@ let%expect_test "to_dir_path" = () ;; +let%expect_test "rem_empty_seg" = + let test path = + let is_dir_path = Absolute_path.is_dir_path path in + let path2 = Absolute_path.rem_empty_seg path in + let is_dir_path2 = Absolute_path.is_dir_path path2 in + print_s + [%sexp + { path : Absolute_path.t; is_dir_path : bool } + , { path2 : Absolute_path.t; is_dir_path2 : bool }] + in + test (Absolute_path.v "/tmp/my-dir/"); + [%expect + {| + (((path /tmp/my-dir/) (is_dir_path true)) + ((path2 /tmp/my-dir) (is_dir_path2 false))) + |}]; + test (Absolute_path.v "/tmp/my-file"); + [%expect + {| + (((path /tmp/my-file) (is_dir_path false)) + ((path2 /tmp/my-file) (is_dir_path2 false))) + |}]; + () +;; + let%expect_test "relativize" = let v str = str |> Fpath.v in let abs = Absolute_path.v in diff --git a/lib/fpath_base/test/test__fpart.ml b/lib/fpath_base/test/test__fpart.ml deleted file mode 100644 index 6f7514f..0000000 --- a/lib/fpath_base/test/test__fpart.ml +++ /dev/null @@ -1,61 +0,0 @@ -let%expect_test "of_string" = - let test str = - print_s [%sexp (Fpart.of_string str : (Fpart.t, [ `Msg of string ]) Result.t)] - in - test ""; - [%expect {| (Error (Msg ": invalid file part")) |}]; - test "a"; - [%expect {| (Ok a) |}]; - test ".a"; - [%expect {| (Ok .a) |}]; - test ".."; - [%expect {| (Ok ..) |}]; - test "/"; - [%expect {| (Error (Msg "/: invalid file part")) |}]; - test "a/b"; - [%expect {| (Error (Msg "a/b: invalid file part")) |}]; - test "a\000b"; - [%expect {| (Error (Msg "a\000b: invalid file part")) |}]; - () -;; - -let%expect_test "hard coded" = - List.iter - ~f:(fun (name, t) -> print_endline (Printf.sprintf "%10s: " name ^ Fpart.to_string t)) - Fpart.[ "dot", dot; "dot_dot", dot_dot; "dot_git", dot_git; "dot_hg", dot_hg ]; - [%expect {| - dot: . - dot_dot: .. - dot_git: .git - dot_hg: .hg |}]; - () -;; - -let%expect_test "hashtbl" = - let t = Hashtbl.create (module Fpart) in - Hashtbl.set t ~key:(Fpart.v "my-file") ~data:42; - print_s [%sexp (t : int Hashtbl.M(Fpart).t)]; - [%expect {| ((my-file 42)) |}] -;; - -module Pair = struct - [@@@coverage off] - - type t = - { a : Fpart.t - ; b : Fpart.t - } - [@@deriving compare, hash, sexp_of] -end - -let%expect_test "hash-fold-t" = - let t = Hashtbl.create (module Pair) in - Hashtbl.set t ~key:{ a = Fpart.v "a"; b = Fpart.v "b" } ~data:42; - print_s [%sexp (t : int Hashtbl.M(Pair).t)]; - [%expect {| - (( - ((a a) - (b b)) - 42)) - |}] -;; diff --git a/lib/fpath_base/test/test__fsegment.ml b/lib/fpath_base/test/test__fsegment.ml new file mode 100644 index 0000000..83016ea --- /dev/null +++ b/lib/fpath_base/test/test__fsegment.ml @@ -0,0 +1,62 @@ +let%expect_test "of_string" = + let test str = + print_s [%sexp (Fsegment.of_string str : (Fsegment.t, [ `Msg of string ]) Result.t)] + in + test ""; + [%expect {| (Error (Msg ": invalid file segment")) |}]; + test "a"; + [%expect {| (Ok a) |}]; + test ".a"; + [%expect {| (Ok .a) |}]; + test ".."; + [%expect {| (Ok ..) |}]; + test "/"; + [%expect {| (Error (Msg "/: invalid file segment")) |}]; + test "a/b"; + [%expect {| (Error (Msg "a/b: invalid file segment")) |}]; + test "a\000b"; + [%expect {| (Error (Msg "a\000b: invalid file segment")) |}]; + () +;; + +let%expect_test "hard coded" = + List.iter + ~f:(fun (name, t) -> + print_endline (Printf.sprintf "%10s: " name ^ Fsegment.to_string t)) + Fsegment.[ "dot", dot; "dot_dot", dot_dot; "dot_git", dot_git; "dot_hg", dot_hg ]; + [%expect {| + dot: . + dot_dot: .. + dot_git: .git + dot_hg: .hg |}]; + () +;; + +let%expect_test "hashtbl" = + let t = Hashtbl.create (module Fsegment) in + Hashtbl.set t ~key:(Fsegment.v "my-file") ~data:42; + print_s [%sexp (t : int Hashtbl.M(Fsegment).t)]; + [%expect {| ((my-file 42)) |}] +;; + +module Pair = struct + [@@@coverage off] + + type t = + { a : Fsegment.t + ; b : Fsegment.t + } + [@@deriving compare, hash, sexp_of] +end + +let%expect_test "hash-fold-t" = + let t = Hashtbl.create (module Pair) in + Hashtbl.set t ~key:{ a = Fsegment.v "a"; b = Fsegment.v "b" } ~data:42; + print_s [%sexp (t : int Hashtbl.M(Pair).t)]; + [%expect {| + (( + ((a a) + (b b)) + 42)) + |}] +;; diff --git a/lib/fpath_base/test/test__fpart.mli b/lib/fpath_base/test/test__fsegment.mli similarity index 100% rename from lib/fpath_base/test/test__fpart.mli rename to lib/fpath_base/test/test__fsegment.mli diff --git a/lib/fpath_base/test/test__hash.ml b/lib/fpath_base/test/test__hash.ml new file mode 100644 index 0000000..6966d41 --- /dev/null +++ b/lib/fpath_base/test/test__hash.ml @@ -0,0 +1,117 @@ +(* Below we verify that the modules are compatible with stdlib hash functors. *) + +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_sexp0.Fpath) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_sexp0.Fpath) +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_sexp0.Fsegment) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_sexp0.Fsegment) +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_sexp0.Absolute_path) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_sexp0.Absolute_path) +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_sexp0.Relative_path) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_sexp0.Relative_path) + +(* If you are using [Fpath_base] we expect that you'll be using base style + containers, however the seeded hash functions are exposed too. *) + +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_base.Fpath) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_base.Fpath) +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_base.Fsegment) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_base.Fsegment) +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_base.Absolute_path) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_base.Absolute_path) +module _ = Stdlib.MoreLabels.Hashtbl.Make (Fpath_base.Relative_path) +module _ = Stdlib.MoreLabels.Hashtbl.MakeSeeded (Fpath_base.Relative_path) + +let%expect_test "hash" = + let seg = Fsegment.v "file" in + let h1 = Fpath_base.Fsegment.hash seg in + let h2 = Fpath_sexp0.Fsegment.hash seg in + print_s [%sexp (h1 : int)]; + [%expect {| 437367475 |}]; + require_equal [%here] (module Int) h1 h2; + [%expect {||}]; + () +;; + +let%expect_test "Fsegment.seeded_hash" = + let seg = Fsegment.v "file" in + let s0 = Fsegment.seeded_hash 0 seg in + let s42 = Fsegment.seeded_hash 42 seg in + print_s [%sexp (s0 : int)]; + [%expect {| 437367475 |}]; + print_s [%sexp (s42 : int)]; + [%expect {| 202913284 |}]; + let f0 = Fsegment.hash_fold_t (Hash.create ()) seg |> Hash.get_hash_value in + let f42 = Fsegment.hash_fold_t (Hash.create ~seed:42 ()) seg |> Hash.get_hash_value in + print_s [%sexp (f0 : int)]; + [%expect {| 437367475 |}]; + print_s [%sexp (f42 : int)]; + [%expect {| 202913284 |}]; + require_equal [%here] (module Int) s0 f0; + require_equal [%here] (module Int) s42 f42; + [%expect {||}]; + () +;; + +let%expect_test "Fpath.seeded_hash" = + let path = Fpath.v "file" in + let s0 = Fpath.seeded_hash 0 path in + let s42 = Fpath.seeded_hash 42 path in + print_s [%sexp (s0 : int)]; + [%expect {| 437367475 |}]; + print_s [%sexp (s42 : int)]; + [%expect {| 202913284 |}]; + let f0 = Fpath.hash_fold_t (Hash.create ()) path |> Hash.get_hash_value in + let f42 = Fpath.hash_fold_t (Hash.create ~seed:42 ()) path |> Hash.get_hash_value in + print_s [%sexp (f0 : int)]; + [%expect {| 437367475 |}]; + print_s [%sexp (f42 : int)]; + [%expect {| 202913284 |}]; + require_equal [%here] (module Int) s0 f0; + require_equal [%here] (module Int) s42 f42; + [%expect {||}]; + () +;; + +let%expect_test "Relative_path.seeded_hash" = + let path = Relative_path.v "file" in + let s0 = Relative_path.seeded_hash 0 path in + let s42 = Relative_path.seeded_hash 42 path in + print_s [%sexp (s0 : int)]; + [%expect {| 437367475 |}]; + print_s [%sexp (s42 : int)]; + [%expect {| 202913284 |}]; + let f0 = Relative_path.hash_fold_t (Hash.create ()) path |> Hash.get_hash_value in + let f42 = + Relative_path.hash_fold_t (Hash.create ~seed:42 ()) path |> Hash.get_hash_value + in + print_s [%sexp (f0 : int)]; + [%expect {| 437367475 |}]; + print_s [%sexp (f42 : int)]; + [%expect {| 202913284 |}]; + require_equal [%here] (module Int) s0 f0; + require_equal [%here] (module Int) s42 f42; + [%expect {||}]; + () +;; + +let%expect_test "Absolute_path.seeded_hash" = + let path = Absolute_path.v "/tmp/my-file" in + let s0 = Absolute_path.seeded_hash 0 path in + let s42 = Absolute_path.seeded_hash 42 path in + print_s [%sexp (s0 : int)]; + [%expect {| 152999615 |}]; + print_s [%sexp (s42 : int)]; + [%expect {| 524647462 |}]; + let f0 = Absolute_path.hash_fold_t (Hash.create ()) path |> Hash.get_hash_value in + let f42 = + Absolute_path.hash_fold_t (Hash.create ~seed:42 ()) path |> Hash.get_hash_value + in + print_s [%sexp (f0 : int)]; + [%expect {| 152999615 |}]; + print_s [%sexp (f42 : int)]; + [%expect {| 524647462 |}]; + require_equal [%here] (module Int) s0 f0; + require_equal [%here] (module Int) s42 f42; + [%expect {||}]; + () +;; diff --git a/lib/fpath_base/test/test__hash.mli b/lib/fpath_base/test/test__hash.mli new file mode 100644 index 0000000..e69de29 diff --git a/lib/fpath_base/test/test__relative_path.ml b/lib/fpath_base/test/test__relative_path.ml index ab2ed4e..c615aea 100644 --- a/lib/fpath_base/test/test__relative_path.ml +++ b/lib/fpath_base/test/test__relative_path.ml @@ -108,7 +108,7 @@ let%expect_test "extend" = let file str = str |> Fpart.v in let test a b = print_s [%sexp (Relative_path.extend a b : Relative_path.t)] in require_does_raise [%here] (fun () : Fpart.t -> file "a/b"); - [%expect {| (Invalid_argument "a/b: invalid file part") |}]; + [%expect {| (Invalid_argument "a/b: invalid file segment") |}]; require_does_not_raise [%here] (fun () -> ignore (file ".." : Fpart.t)); [%expect {| |}]; test Relative_path.empty (file "a"); @@ -292,6 +292,31 @@ let%expect_test "to_dir_path" = () ;; +let%expect_test "rem_empty_seg" = + let test path = + let is_dir_path = Relative_path.is_dir_path path in + let path2 = Relative_path.rem_empty_seg path in + let is_dir_path2 = Relative_path.is_dir_path path2 in + print_s + [%sexp + { path : Relative_path.t; is_dir_path : bool } + , { path2 : Relative_path.t; is_dir_path2 : bool }] + in + test (Relative_path.v "tmp/my-dir/"); + [%expect + {| + (((path tmp/my-dir/) (is_dir_path true)) + ((path2 tmp/my-dir) (is_dir_path2 false))) + |}]; + test (Relative_path.v "tmp/my-file"); + [%expect + {| + (((path tmp/my-file) (is_dir_path false)) + ((path2 tmp/my-file) (is_dir_path2 false))) + |}]; + () +;; + let%expect_test "hashtbl" = let t = Hashtbl.create (module Relative_path) in Hashtbl.set t ~key:(Relative_path.v "path/to/my-file") ~data:42; diff --git a/lib/fpath_sexp0/src/fpath0.ml b/lib/fpath_sexp0/src/fpath0.ml index 80dfb95..bac3662 100644 --- a/lib/fpath_sexp0/src/fpath0.ml +++ b/lib/fpath_sexp0/src/fpath0.ml @@ -3,3 +3,5 @@ type t = Fpath.t let sexp_of_t t = Sexplib0.Sexp.Atom (Fpath.to_string t) let compare = Fpath.compare let equal = Fpath.equal +let hash t = String.hash (Fpath.to_string t) +let seeded_hash seed t = String.seeded_hash seed (Fpath.to_string t) diff --git a/lib/fpath_sexp0/src/fpath0.mli b/lib/fpath_sexp0/src/fpath0.mli index 270952f..3cdb8cd 100644 --- a/lib/fpath_sexp0/src/fpath0.mli +++ b/lib/fpath_sexp0/src/fpath0.mli @@ -5,3 +5,5 @@ type t = Fpath.t val sexp_of_t : t -> Sexplib0.Sexp.t val compare : t -> t -> int val equal : t -> t -> bool +val hash : t -> int +val seeded_hash : int -> t -> int diff --git a/lib/fpath_sexp0/src/fpath_sexp0.ml b/lib/fpath_sexp0/src/fpath_sexp0.ml index f9035e3..7e1a967 100644 --- a/lib/fpath_sexp0/src/fpath_sexp0.ml +++ b/lib/fpath_sexp0/src/fpath_sexp0.ml @@ -6,4 +6,5 @@ end module Absolute_path = Path.Absolute_path module Relative_path = Path.Relative_path -module Fpart = Fpart +module Fsegment = Fsegment +module Fpart = Fsegment diff --git a/lib/fpath_sexp0/src/fpath_sexp0.mli b/lib/fpath_sexp0/src/fpath_sexp0.mli index d7e0a0f..fb2485f 100644 --- a/lib/fpath_sexp0/src/fpath_sexp0.mli +++ b/lib/fpath_sexp0/src/fpath_sexp0.mli @@ -8,4 +8,8 @@ end module Absolute_path = Path.Absolute_path module Relative_path = Path.Relative_path -module Fpart = Fpart +module Fsegment = Fsegment + +(** This alias is kept for backward compatibility for now but will soon be + deprecated. Please upgrade code to [Fsegment]. *) +module Fpart = Fsegment diff --git a/lib/fpath_sexp0/src/fpart.ml b/lib/fpath_sexp0/src/fsegment.ml similarity index 53% rename from lib/fpath_sexp0/src/fpart.ml rename to lib/fpath_sexp0/src/fsegment.ml index 5e5d75c..d742865 100644 --- a/lib/fpath_sexp0/src/fpart.ml +++ b/lib/fpath_sexp0/src/fsegment.ml @@ -3,16 +3,25 @@ type t = string let compare = String.compare let equal = String.equal let sexp_of_t t = Sexplib0.Sexp.Atom t +let hash = String.hash +let seeded_hash = String.seeded_hash +let dir_sep_char = Fpath.dir_sep.[0] + +let no_dir_sep = + if Char.equal dir_sep_char '/' + then fun c -> not (Char.equal c '/') + else fun [@coverage off] c -> not (Char.equal c '/' || Char.equal c dir_sep_char) +;; let invariant t = String.length t > 0 - && String.for_all (fun c -> not (Char.equal c '/' || Char.equal c '\000')) t + && String.for_all (fun c -> no_dir_sep c && not (Char.equal c '\000')) t ;; let to_string t = t let of_string s = - if invariant s then Ok s else Error (`Msg (Printf.sprintf "%s: invalid file part" s)) + if invariant s then Ok s else Error (`Msg (Printf.sprintf "%s: invalid file segment" s)) ;; let v t = diff --git a/lib/fpath_sexp0/src/fpart.mli b/lib/fpath_sexp0/src/fsegment.mli similarity index 57% rename from lib/fpath_sexp0/src/fpart.mli rename to lib/fpath_sexp0/src/fsegment.mli index f72251b..567ff6e 100644 --- a/lib/fpath_sexp0/src/fpart.mli +++ b/lib/fpath_sexp0/src/fsegment.mli @@ -1,21 +1,26 @@ (** Part of a file path. - A [Fpart.t] represents a segment of a file path, i.e., the parts of the + A [Fsegment.t] represents a segment of a file path, i.e., the parts of the path that are separated by the directory separator character. - For example, in the file path ["/home/user/documents/file.txt"], - the parts are [["home" ; "user" ; "documents" ; "file.txt"]]. + For example, in the file path ["/home/user/documents/file.txt"], the + segments are [["home" ; "user" ; "documents" ; "file.txt"]]. - A valid file part cannot contain ['/'] or null characters. + A valid file segment cannot contain ['/'], the directory separator char or + null characters. - This module provides functions to convert between strings and file parts, - validate file parts, and some common file parts. *) + By contrast to Fpath's [seg], a [Fsegment.t] may not be empty. + + This module provides functions to convert between strings and file segments, + validate segments, and some common file segments. *) type t val sexp_of_t : t -> Sexplib0.Sexp.t val compare : t -> t -> int val equal : t -> t -> bool +val hash : t -> int +val seeded_hash : int -> t -> int val of_string : string -> (t, [ `Msg of string ]) Result.t val to_string : t -> string val v : string -> t diff --git a/lib/fpath_sexp0/src/path.ml b/lib/fpath_sexp0/src/path.ml index 1f7b414..f3a3d0f 100644 --- a/lib/fpath_sexp0/src/path.ml +++ b/lib/fpath_sexp0/src/path.ml @@ -2,17 +2,19 @@ type absolute_path = Fpath.t type relative_path = Fpath.t let append a r = Fpath.(a // r) |> Fpath.normalize -let extend t f = Fpath.(t / Fpart.to_string f) |> Fpath.normalize +let extend t f = Fpath.(t / Fsegment.to_string f) |> Fpath.normalize let parent t = let t' = Fpath.normalize t |> Fpath.parent in if Fpath.equal t t' then None else Some t' ;; +let empty_rel_path = Fpath.v ("." ^ Fpath.dir_sep) + let chop_prefix t ~prefix = match Fpath.rem_prefix prefix t with | Some t -> Some t - | None -> if Fpath.equal prefix t then Some Fpath.(v "./") else None + | None -> if Fpath.equal prefix t then Some empty_rel_path else None ;; let chop_suffix ~empty t ~suffix = @@ -65,6 +67,7 @@ module Absolute_path = struct let chop_suffix t ~suffix = chop_suffix ~empty:root t ~suffix let is_dir_path = Fpath.is_dir_path let to_dir_path = Fpath.to_dir_path + let rem_empty_seg = Fpath.rem_empty_seg let relativize ~root f = let f = Fpath.normalize f in @@ -99,7 +102,7 @@ module Relative_path = struct | Error (`Msg m) -> invalid_arg m ;; - let empty = Fpath.v "./" + let empty = empty_rel_path let append = append let extend = extend let parent = parent @@ -108,6 +111,7 @@ module Relative_path = struct let chop_suffix t ~suffix = chop_suffix ~empty t ~suffix let is_dir_path = Fpath.is_dir_path let to_dir_path = Fpath.to_dir_path + let rem_empty_seg = Fpath.rem_empty_seg end module Export = struct diff --git a/lib/fpath_sexp0/src/path.mli b/lib/fpath_sexp0/src/path.mli index d13fdb0..7e5dd77 100644 --- a/lib/fpath_sexp0/src/path.mli +++ b/lib/fpath_sexp0/src/path.mli @@ -11,6 +11,8 @@ module Absolute_path : sig val sexp_of_t : t -> Sexplib0.Sexp.t val compare : t -> t -> int val equal : t -> t -> bool + val hash : t -> int + val seeded_hash : int -> t -> int val to_fpath : t -> Fpath.t val to_string : t -> string @@ -28,12 +30,13 @@ module Absolute_path : sig val root : t val append : t -> relative_path -> t - val extend : t -> Fpart.t -> t + val extend : t -> Fsegment.t -> t val parent : t -> t option val chop_prefix : t -> prefix:t -> relative_path option val chop_suffix : t -> suffix:relative_path -> t option val is_dir_path : t -> bool val to_dir_path : t -> t + val rem_empty_seg : t -> t (** Converts a Path.t to an Absolute_path.t: - If the path is already absolute, that's the answer. @@ -47,6 +50,8 @@ module Relative_path : sig val sexp_of_t : t -> Sexplib0.Sexp.t val compare : t -> t -> int val equal : t -> t -> bool + val hash : t -> int + val seeded_hash : int -> t -> int val to_fpath : t -> Fpath.t val to_string : t -> string @@ -66,13 +71,14 @@ module Relative_path : sig val empty : t val append : t -> t -> t - val extend : t -> Fpart.t -> t + val extend : t -> Fsegment.t -> t val parent : t -> t option - val of_list : Fpart.t list -> t + val of_list : Fsegment.t list -> t val chop_prefix : t -> prefix:t -> t option val chop_suffix : t -> suffix:t -> t option val is_dir_path : t -> bool val to_dir_path : t -> t + val rem_empty_seg : t -> t end (** This module is re-exported as part of the [Fpath] module. For example: