dune/src/path.ml

411 lines
9.4 KiB
OCaml

open Import
let explode_path =
let rec loop path acc =
let dir = Filename.dirname path in
let base = Filename.basename path in
let acc = base :: acc in
if dir = Filename.current_dir_name then
acc
else
loop dir acc
in
fun path -> loop path []
module External = struct
type t = string
let to_string t = t
(*
let rec cd_dot_dot t =
match Unix.readlink t with
| exception _ -> Filename.dirname t
| t -> cd_dot_dot t
let relative initial_t path =
let rec loop t components =
match components with
| [] | ["." | ".."] ->
die "invalid filename concatenation: %s / %s" initial_t path
| [fn] -> Filename.concat t fn
| "." :: rest -> loop t rest
| ".." :: rest -> loop (cd_dot_dot t) rest
| comp :: rest -> loop (Filename.concat t comp) rest
in
loop initial_t (explode_path path)
*)
let relative = Filename.concat
end
let is_root = function
| "" -> true
| _ -> false
module Local = struct
(* either "" for root, either a '/' separated list of components other that ".", ".."
and not containing '/'. *)
type t = string
let root = ""
let is_root = function
| "" -> true
| _ -> false
let to_string = function
| "" -> "."
| t -> t
let compare = String.compare
module Set = String_set
let to_list =
let rec loop t acc i j =
if i = 0 then
String.sub t ~pos:0 ~len:j :: acc
else
match t.[i - 1] with
| '/' -> loop t (String.sub t ~pos:i ~len:(j - i) :: acc) (i - 1) (i - 1)
| _ -> loop t acc (i - 1) j
in
function
| "" -> []
| t ->
let len = String.length t in
loop t [] len len
let parent = function
| "" ->
code_errorf "Path.Local.parent called on the root"
| t ->
match String.rindex_from t (String.length t - 1) '/' with
| exception Not_found -> ""
| i -> String.sub t ~pos:0 ~len:i
let basename = function
| "" ->
code_errorf "Path.Local.basename called on the root"
| t ->
let len = String.length t in
match String.rindex_from t (len - 1) '/' with
| exception Not_found -> t
| i -> String.sub t ~pos:(i + 1) ~len:(len - i - 1)
let relative initial_t path =
let rec loop t components =
match components with
| [] -> t
| "." :: rest -> loop t rest
| ".." :: rest ->
begin match t with
| "" ->
die "path outside the workspace: %s from %s" path
(to_string initial_t)
| t -> loop (parent t) rest
end
| fn :: rest ->
match t with
| "" -> loop fn rest
| _ -> loop (t ^ "/" ^ fn) rest
in
loop initial_t (explode_path path)
let is_canonicalized =
let rec before_slash s i =
if i < 0 then
false
else
match s.[i] with
| '/' -> false
| '.' -> before_dot_slash s (i - 1)
| _ -> in_component s (i - 1)
and before_dot_slash s i =
if i < 0 then
false
else
match s.[i] with
| '/' -> false
| '.' -> before_dot_dot_slash s (i - 1)
| _ -> in_component s (i - 1)
and before_dot_dot_slash s i =
if i < 0 then
false
else
match s.[i] with
| '/' -> false
| _ -> in_component s (i - 1)
and in_component s i =
if i < 0 then
true
else
match s.[i] with
| '/' -> before_slash s (i - 1)
| _ -> in_component s (i - 1)
in
fun s ->
let len = String.length s in
if len = 0 then
true
else
before_slash s (len - 1)
let of_string s =
if is_canonicalized s then
s
else
relative "" s
let rec mkdir_p = function
| "" -> ()
| t ->
try
Unix.mkdir t 0o777
with
| Unix.Unix_error (EEXIST, _, _) -> ()
| Unix.Unix_error (ENOENT, _, _) as e ->
match parent t with
| "" -> raise e
| p ->
mkdir_p p;
Unix.mkdir t 0o777
let ensure_parent_directory_exists = function
| "" -> ()
| t -> mkdir_p (parent t)
let append a b =
match a, b with
| "", x | x, "" -> x
| _ -> a ^ "/" ^ b
let descendant t ~of_ =
match of_ with
| "" -> Some t
| _ ->
let of_len = String.length of_ in
let t_len = String.length t in
if (t_len = of_len && t = of_) ||
(t_len >= of_len && t.[of_len] = '/' && String.is_prefix t ~prefix:of_) then
Some (String.sub t ~pos:(of_len + 1) ~len:(t_len - of_len - 1))
else
None
let is_descendant t ~of_ =
match of_ with
| "" -> true
| _ ->
let of_len = String.length of_ in
let t_len = String.length t in
(t_len = of_len && t = of_) ||
(t_len >= of_len && t.[of_len] = '/' && String.is_prefix t ~prefix:of_)
let reach t ~from =
let rec loop t from =
match t, from with
| a :: t, b :: from when a = b ->
loop t from
| _ ->
match List.fold_left from ~init:t ~f:(fun acc _ -> ".." :: acc) with
| [] -> "."
| l -> String.concat l ~sep:"/"
in
loop (to_list t) (to_list from)
end
type t = string
let compare = String.compare
module Set = String_set
module Map = String_map
module Kind = struct
type t =
| External of External.t
| Local of Local.t
end
let is_local t = is_root t || Filename.is_relative t
let kind t : Kind.t =
if is_local t then
Local t
else
External t
let to_string = function
| "" -> "."
| t -> t
let root = ""
let relative t fn =
if fn = "" then
t
else
match is_local t, is_local fn with
| true, true -> Local.relative t fn
| _ , false -> fn
| false, true -> External.relative t fn
let of_string = function
| "" -> ""
| s ->
if Filename.is_relative s then
Local.of_string s
else
s
let t sexp = of_string (Sexp.Of_sexp.string sexp)
let sexp_of_t t = Sexp.Atom (to_string t)
let absolute =
let initial_dir = Sys.getcwd () in
fun fn ->
if is_local fn then
Filename.concat initial_dir fn
else
fn
let reach t ~from =
match is_local t, is_local from with
| false, _ -> t
| true, false ->
Sexp.code_error "Path.reach called with invalid combination"
[ "t" , sexp_of_t t
; "from", sexp_of_t from
]
| true, true -> Local.reach t ~from
let reach_for_running t ~from =
match is_local t, is_local from with
| false, _ -> t
| true, false ->
Sexp.code_error "Path.reach_for_running called with invalid combination"
[ "t" , sexp_of_t t
; "from", sexp_of_t from
]
| true, true ->
let s = Local.reach t ~from in
if String.is_prefix s ~prefix:"../" then
s
else
"./" ^ s
let descendant t ~of_ =
if is_local t && is_local of_ then
Local.descendant t ~of_
else
None
let is_descendant t ~of_ =
if is_local t && is_local of_ then
Local.is_descendant t ~of_
else
false
let append a b =
if not (is_local b) then
Sexp.code_error "Path.append called with non-local second path"
[ "a", sexp_of_t a
; "b", sexp_of_t b
];
if is_local a then
Local.append a b
else
Filename.concat a b
let basename t =
if is_local t then
Local.basename t
else
Filename.basename t
let parent t =
if is_local t then
Local.parent t
else
Filename.dirname t
let build_prefix = "_build/"
let is_in_build_dir t =
String.is_prefix t ~prefix:build_prefix
let extract_build_context t =
if String.is_prefix t ~prefix:build_prefix then
let i = String.length build_prefix in
match String.index_from t i '/' with
| exception _ ->
Some
(String.sub t ~pos:i ~len:(String.length t - i),
"")
| j ->
Some
(String.sub t ~pos:i ~len:(j - i),
String.sub t ~pos:(j + 1) ~len:(String.length t - j - 1))
else
None
let extract_build_context_dir t =
if String.is_prefix t ~prefix:build_prefix then
let i = String.length build_prefix in
match String.index_from t i '/' with
| exception _ ->
Some (t, "")
| j ->
Some
(String.sub t ~pos:0 ~len:j,
String.sub t ~pos:(j + 1) ~len:(String.length t - j - 1))
else
None
let drop_build_context t =
match extract_build_context t with
| None -> t
| Some (_, t) -> t
let exists t = Sys.file_exists (to_string t)
let readdir t = Sys.readdir (to_string t) |> Array.to_list
let is_directory t = Sys.is_directory (to_string t)
let rmdir t = Unix.rmdir (to_string t)
let unlink t = Unix.unlink (to_string t)
let unlink_no_err t = try Unix.unlink (to_string t) with _ -> ()
let extend_basename t ~suffix = t ^ suffix
let insert_after_build_dir_exn =
let error a b =
Sexp.code_error
"Path.insert_after_build_dir_exn"
[ "path" , Atom a
; "insert", Atom b
]
in
fun a b ->
if not (is_local a && is_local b) then error a b;
match String.lsplit2 a ~on:'/' with
| Some ("_build", rest) ->
if is_root b then
a
else
sprintf "_build/%s/%s" b rest
| _ ->
error a b
let rm_rf =
let rec loop dir =
Array.iter (Sys.readdir dir) ~f:(fun fn ->
let fn = Filename.concat dir fn in
match Unix.lstat fn with
| { st_kind = S_DIR; _ } -> loop fn
| _ -> Unix.unlink fn);
Unix.rmdir dir
in
fun t ->
let fn = to_string t in
match Unix.lstat fn with
| exception Unix.Unix_error(ENOENT, _, _) -> ()
| _ -> loop fn