Fpath
File system paths, file extensions, path sets and maps.
A (file system) path specifies a file or a directory in a file system hierarchy. A path has three parts:
dir_sep
whose presence distinguishes absolute paths ("/a"
) from relative ones ("a"
)dir_sep
separated segments. Segments are non empty strings except for maybe the last one. The latter distinguishes directory paths ("a/b/"
) from file paths ("a/b"
).The path segments "."
and ".."
are relative path segments that respectively denote the current and parent directory. The basename of a path is its last non-empty segment if it is not a relative path segment or the empty string otherwise.
Consult a few important tips.
Note. Fpath
processes paths without accessing the file system.
v0.7.3 - homepage
dir_sep
is the platform dependent natural directory separator. This is "/"
on POSIX and "\\"
on Windows.
is_seg s
is true
iff s
does not contain dir_sep
or '/'
or a 0x00
byte.
is_rel_seg s
is true iff s
is a relative segment, that is "."
or ".."
.
val v : string -> t
v s
is the string s
as a path.
add_seg p seg
adds segment seg
to the segments of p
if p
's last segment is non-empty or replaces the last empty segment with seg
. Examples.
split_volume p
is the pair (vol, q)
where vol
is the platform dependent volume of p
or the empty string if there is none and q
the path p
without its volume, that is its optional root dir_sep
and segments.
On POSIX if vol
is non-empty then it can only be "/"
(e.g. in v "//a/b"
). On Windows vol
may be one of the following prefixes parsed before an absolute root dir_sep
, except in the first case where a relative path can follow:
$(drive):
\\$(server)\$(share)
\\?\$(drive):
\\?\$(server)\$(share)
\\?\UNC\$(server)\$(share)
\\.\$(device)
The following invariant holds:
equal p (v @@ vol ^ (to_string q))
val segs : t -> string list
Note. The following functions use syntactic semantic properties of paths. Given a path, these properties can be different from the one your file system attributes to it.
val is_dir_path : t -> bool
is_dir_path p
is true
iff p
represents a directory. This means that p
's last segment is either empty (""
) or relative. The property is invariant with respect to normalization. Examples.
val is_file_path : t -> bool
is_file_path p
is true
iff p
represents a file. This is the negation of is_dir_path
. This means that p
's last segment is neither empty (""
) nor relative. The property is invariant with respect to normalization. Examples.
val filename : t -> string
filename p
is the file name of p
. This is the last segment of p
if p
is a file path and the empty string otherwise. The result is invariant with respect to normalization. See also basename
. Examples.
split_base p
splits p
into a directory d
and a relative base path b
such that:
b
is a relative path that contains the segments of p
that start at the last non-empty segment. This means that b
has a single non-empty segment, and preserves directoryness of p
. If p
is a root path there are no such segments and b
is "./"
.d
is a directory such that d // b
represents the same path as p
. They may however differ syntactically when converted to a string.Note. Normalizing p
before using the function ensures that b
is a relative segment iff p
cannot be named (like in "."
, "../../"
, "/"
, etc.).
val basename : t -> string
basename p
is p
's last non-empty segment if non-relative or the empty string otherwise. The latter occurs only on root paths and on paths whose last non-empty segment is a relative segment. See also filename
and base
. Examples.
Note. Normalizing p
before using the function ensures the empty string is only returned iff p
cannot be named (like in "."
, "../../"
, "/"
, etc.)
parent p
is a directory path that contains p
. If p
is a root path this is p
itself. Examples.
Warning. parent p // base p
may not represent p
, use split_base
for this.
rem_empty_seg p
removes an existing last empty segment of p
if p
is not a root path. This ensure that if p
is converted to a string it will not have a trailing dir_sep
unless p
is a root path. Note that this may affect p
's directoryness. Examples.
normalize p
is a path that represents the same path as p
, directoryness included, and that has the following properties:
p
is absolute the resulting path has no "."
and ".."
segments.p
is relative the resulting path is either "./"
or it has no "."
segments and ".."
segments may only appear as initial segments.p
is a directory it always end with an empty segment; this means it doesn't end with "."
or ".."
.Warning. Like file and directory path functions this function does not consult the file system and is purely based on the syntactic semantic of paths which can be different from the one of your concrete file system attributes. For example in presence of symbolic links the resulting path may not point to the same entity. Use the normalization functions of your OS system library to ensure correct behaviour with respect to a concrete file system.
Warning. The syntactic prefix relation between paths does not, in general, entail directory containement. The following examples show this:
is_prefix (v "..") (v "../..") = true
is_prefix (v "..") (v ".") = false
However, on normalized, absolute paths, the prefix relation does entail directory containement. See also is_rooted
.
is_prefix prefix p
is true
if prefix
is a prefix of p
. This checks that:
prefix
has the same optional volume as p
.prefix
has the same optional root directory separator as p
.prefix
is a prefix of those of p
, ignoring the last empty segment of prefix
if the number of non-empty segments of p
is strictly larger than those of prefix
. This means that is_prefix (v "a/") (v "a/b")
is true
but is_prefix (v "a/") (v "a")
is false
find_prefix p p'
is Some prefix
if there exists prefix
such that prefix
is the longest path with is_prefix prefix p &&
is_prefix prefix p' = true
and None
otherwise. Note that if both p
and p'
are absolute and have the same volume then a prefix always exists: the root path of their volume. Examples.
rem_prefix prefix p
is:
None
if prefix
is not a prefix of p
or if prefix
and p
are equal.Some q
otherwise where q
is p
without the prefix prefix
and preserves p
's directoryness. This means that q
is a always relative and that the path prefix // q
and p
represent the same paths. They may however differ syntactically when converted to a string.relativize ~root p
is:
Some q
if there exists a relative path q
such that root // q
and p
represent the same paths, directoryness included. They may however differ syntactically when converted to a string. Note that q
is normalized.None
otherwise.is_rooted root p
is true
iff the path p
is the directory root
or contained in root
and that p
can be relativized w.r.t. root
(the normalized relative path will have no parent directory segments). Examples.
val is_rel : t -> bool
is_rel p
is true
iff p
is a relative path, i.e. the root directory separator is missing in p
.
val is_abs : t -> bool
is_abs p
is true
iff p
is an absolute path, i.e. the root directory separator is present in p
.
val is_root : t -> bool
is_root p
is true
iff p
is a root directory, i.e. p
has the root directory separator and a single, empty, segment. Examples.
Warning. By definition this is a syntactic test. For example it will return false
on "/a/.."
or "/.."
. Normalizing the path before testing avoids this problem.
val is_current_dir : ?prefix:bool -> t -> bool
is_current_dir p
is true iff p
is the current relative directory, i.e. either "."
or "./"
. If prefix
is true
(defaults to false
) simply checks that p
is relative and its first segment is "."
.
Warning. By definition this is a syntactic test. For example it will return false
on "./a/.."
or "./."
. Normalizing the path before testing avoids this problem.
val is_parent_dir : ?prefix:bool -> t -> bool
is_parent_dir p
is true
iff p
is the relative parent directory, i.e. either ".."
or "../"
. If prefix
is true
(defaults to false
), simply checks that p
is relative and its first segment is ".."
.
Warning. By definition this is a syntactic test. For example it will return false
on "./a/../.."
or "./.."
. Normalizing the path before testing avoids this problem.
val is_dotfile : t -> bool
is_dotfile p
is true
iff p
's basename is non empty and starts with a '.'
.
Warning. By definition this is a syntactic test. For example it will return false
on ".ssh/."
. Normalizing the path before testing avoids this problem.
equal p p'
is true
if p
and p'
have the same volume are both relative or absolute and have the same segments.
Warning. By definition this is a syntactic test. For example equal (v "./") (v "a/..")
is false
. Normalizing the paths before testing avoids this problem.
val to_string : t -> string
to_string p
is the path p
as a string. The result can be safely converted back with v
.
of_string s
is the string s
as a path. The following transformations are performed on the string:
'/'
occurence is converted to '\\'
before any processing occurs."a//b"
becomes "a/b"
, "//a////b//"
becomes "//a/b/"
, etc."\\\\server\\share"
becomes "\\\\server\\share\\"
, but incomplete UNC volumes like "\\\\a"
return Error
.Error (`Msg (strf "%S: invalid path" s))
is returned if
s
or the path following the volume is empty (""
), except on Windows UNC paths, see above.s
has null byte ('\x00'
).s
is an invalid UNC path (e.g. "\\\\"
or "\\\\a"
)val pp : Format.formatter -> t -> unit
pp ppf p
prints path p
on ppf
using to_string
.
val dump : Format.formatter -> t -> unit
dump ppf p
prints path p
on ppf
using String
.dump.
The file extension (resp. multiple file extension) of a path segment is the suffix that starts at the last (resp. first) occurence of a '.'
that is preceeded by at least one non '.'
character. If there is no such occurence in the segment, the extension is empty. With these definitions, "."
, ".."
, "..."
and dot files like ".ocamlinit"
or "..ocamlinit"
have no extension, but ".emacs.d"
and "..emacs.d"
do have one.
Warning. The following functions act on paths whose basename is non empty and do nothing otherwise. Normalizing p
before using the functions ensures that the functions do nothing iff p
cannot be named, see basename
.
has_ext e p
is true
iff get_ext p = e || get_ext ~multi:true p = e
. If e
doesn't start with a '.'
one is prefixed before making the test. Examples.
mem_ext exts p
is List.mem (get_ext p) exts || List.mem (get_ext ~multi:true p) exts
.
val exists_ext : ?multi:bool -> t -> bool
split_ext ?multi p
is (rem_ext ?multi p, get_ext ?multi p)
. If this is (q, ext)
the following invariant holds:
equal p (add_ext q ext)
type path = t
The type for path sets. Membership is determined according to equal
.
module Set : sig ... end
Path sets.
module Map : sig ... end
Path maps.
type +'a map = 'a Map.t
The type for maps from paths to values of type 'a
. Paths are compared with compare
.
"a"
) or a directory path (e.g. "a/"
).'\\'
and '/'
as directory separator. However Fpath
on Windows converts '/'
to '\\'
on the fly. Therefore you should either use '/'
for defining constant paths you inject with v
or better, construct them directly with (/)
. to_string
then converts paths to strings using the platform's specific directory separator dir_sep
."/"
. On Windows each volume can have a root path. Use is_root
on normalized paths to detect roots.to_string
to construct URIs, to_string
uses dir_sep
to separate segments, on Windows this is '\\'
which is not what URIs expect. Access path segments directly with segs
; note that you will need to percent encode these.equal (add_seg (v "/a") "b") (v "/a/b")
equal (add_seg (v "/a/") "b") (v "/a/b")
equal (add_seg (v "/a/b") "") (v "/a/b/")
equal (add_seg (v "/a/b/") "") (v "/a/b/")
equal (add_seg (v "/") "") (v "/")
equal (add_seg (v "/") "a") (v "/a")
equal (add_seg (v ".") "") (v "./")
equal (add_seg (v ".") "a") (v "./a")
equal (add_seg (v "..") "") (v "../")
equal (add_seg (v "..") "a") (v "../a")
equal (append (v "/a/b/") (v "e/f")) (v "/a/b/e/f")
equal (append (v "/a/b") (v "e/f")) (v "/a/b/e/f")
equal (append (v "/a/b/") (v "/e/f")) (v "/e/f")
equal (append (v "a/b/") (v "e/f")) (v "a/b/e/f")
equal (append (v "a/b") (v "C:e")) (v "C:e")
(Windows)segs (v "/a/b/") = [""; "a"; "b"; ""]
segs (v "/a/b") = [""; "a"; "b"]
segs (v "a/b/") = ["a"; "b"; ""]
segs (v "a/b") = ["a"; "b"]
segs (v "a") = ["a"]
segs (v "/") = [""; ""]
segs (v "\\\\.\\dev\\") = ["";""]
(Windows)segs (v "\\\\server\\share\\a") = ["";"a"]
(Windows)segs (v "C:a") = ["a"]
(Windows)segs (v "C:\\a") = ["";"a"]
(Windows)is_dir_path (v ".") = true
is_dir_path (v "..") = true
is_dir_path (v "../") = true
is_dir_path (v "/") = true
is_dir_path (v "/a/b/") = true
is_dir_path (v "/a/b") = false
is_dir_path (v "a/") = true
is_dir_path (v "a") = false
is_dir_path (v "a/.") = true
is_dir_path (v "a/..") = true
is_dir_path (v "a/..b") = false
is_dir_path (v "C:\\") = true
(Windows)is_dir_path (v "C:a") = false
(Windows)is_file_path (v ".") = false
is_file_path (v "..") = false
is_file_path (v "../") = false
is_file_path (v "/") = false
is_file_path (v "/a/b/") = false
is_file_path (v "/a/b") = true
is_file_path (v "a/") = false
is_file_path (v "a") = true
is_file_path (v "a/.") = false
is_file_path (v "a/..") = false
is_file_path (v "a/..b") = true
is_file_path (v "C:\\") = false
(Windows)is_file_path (v "C:a") = true
(Windows)equal (to_dir_path @@ v ".") (v "./")
equal (to_dir_path @@ v "..") (v "../")
equal (to_dir_path @@ v "../") (v "../")
equal (to_dir_path @@ v "/") (v "/")
equal (to_dir_path @@ v "/a/b/") (v "/a/b/")
equal (to_dir_path @@ v "/a/b") (v "/a/b/")
equal (to_dir_path @@ v "a/") (v "a/")
equal (to_dir_path @@ v "a") (v "a/")
equal (to_dir_path @@ v "a/.") (v "a/./")
equal (to_dir_path @@ v "a/..") (v "a/../")
equal (to_dir_path @@ v "a/..b") (v "a/..b/")
equal (to_dir_path @@ v "\\\\server\\share\\")
(v "\\\\server\\share\\")
(Windows)equal (to_dir_path @@ v "C:a") (v "C:a\\")
(Windows)equal (to_dir_path @@ v "C:\\") (v "C:\\")
(Windows)filename (v ".") = ""
filename (v "./") = ""
filename (v "..") = ""
filename (v "../") = ""
filename (v "../..") = ""
filename (v "/") = ""
filename (v "/a/b/") = ""
filename (v "/a/b") = "b"
filename (v "a/") = ""
filename (v "a") = "a"
filename (v "a/.") = ""
filename (v "a/..") = ""
filename (v "a/..b") = "..b"
filename (v "C:\\") = ""
(Windows)filename (v "C:a") = "a"
(Windows)(split_base @@ v ".") = (v "./"), (v ".")
(split_base @@ v "./") = (v "./"), (v "./")
(split_base @@ v "..") = (v "./"), (v "..")
(split_base @@ v "../") = (v "./"), (v "../")
(split_base @@ v "../../") = (v "../"), (v "../")
(split_base @@ v ".././") = (v "../"), (v "./")
(split_base @@ v "../../../") = (v "../../"), (v "../")
(split_base @@ v "/") = (v "/"), (v "./")
(split_base @@ v "/a/b/") = (v "/a/"), (v "b/")
(split_base @@ v "/a/b") = (v "/a/"), (v "b")
(split_base @@ v "a/") = (v "./"), (v "a/")
(split_base @@ v "a") = (v "./"), (v "a")
(split_base @@ v "a/b") = (v "a/"), (v "b")
(split_base @@ v "a/b/") = (v "a/b/"), (v "b/")
(split_base @@ v "a/.") = (v "a/"), (v ".")
(split_base @@ v "a/..") = (v "a/"), (v "..")
(split_base @@ v "a/../..") = (v "a/../"), (v "..")
(split_base @@ v "a/..b") = (v "a/"), (v "..b")
(split_base @@ v "./a") = (v "./"), (v "a")
(split_base @@ v "./a/") = (v "./"), (v "a/")
(split_base @@ v "../a") = (v "../"), (v "a")
(split_base @@ v "../a/") = (v "../"), (v "a/")
basename (v ".") = ""
basename (v "..") = ""
basename (v "../") = ""
basename (v "../../") = ""
basename (v "/") = ""
basename (v "/a/b/") = "b"
basename (v "/a/b") = "b"
basename (v "a/") = "a"
basename (v "a") = "a"
basename (v "a/.") = ""
basename (v "a/./") = ""
basename (v "a/..") = ""
basename (v "a/..b") = "..b"
basename (v "./a") = "a"
basename (v "../a") = "a"
basename (v "C:\\") = ""
(Windows)basename (v "C:a") = "a"
(Windows)equal (parent @@ v ".") (v "./../")
equal (parent @@ v "..") (v "../../")
equal (parent @@ v "../") (v "../../")
equal (parent @@ v "../../") (v "../../../")
equal (parent @@ v "/") (v "/")
equal (parent @@ v "/a/b/") (v "/a/")
equal (parent @@ v "/a/b") (v "/a/")
equal (parent @@ v "a/") (v "./")
equal (parent @@ v "a") (v "./")
equal (parent @@ v "a/.") (v "a/./../")
equal (parent @@ v "a/./") (v "a/./../")
equal (parent @@ v "a/..") (v "a/../../")
equal (parent @@ v "a/../") (v "a/../../")
equal (parent @@ v "a/..b") (v "a/")
equal (parent @@ v "./a") (v "./")
equal (parent @@ v "../a") (v "../")
equal (parent @@ v "../../a") (v "../../")
equal (parent @@ v "\\\\server\\share\\") (v "\\\\server\\share\\")
(Windows)equal (parent @@ v "C:\\") (v "C:\\")
(Windows)equal (parent @@ v "C:a") (v "C:.\\")
(Windows)equal (rem_empty_seg @@ v ".") (v ".")
equal (rem_empty_seg @@ v "..") (v "..")
equal (rem_empty_seg @@ v "../") (v "..")
equal (rem_empty_seg @@ v "../../") (v "../..")
equal (rem_empty_seg @@ v "/") (v "/")
equal (rem_empty_seg @@ v "/a/b/") (v "/a/b")
equal (rem_empty_seg @@ v "/a/b") (v "/a/b")
equal (rem_empty_seg @@ v "a/") (v "a")
equal (rem_empty_seg @@ v "a") (v "a")
equal (rem_empty_seg @@ v "a/.") (v "a/.")
equal (rem_empty_seg @@ v "a/./") (v "a/.")
equal (rem_empty_seg @@ v "a/..") (v "a/..")
equal (rem_empty_seg @@ v "a/../") (v "a/..")
equal (rem_empty_seg @@ v "a/..b") (v "a/..b")
equal (rem_empty_seg @@ v "./a") (v "./a")
equal (rem_empty_seg @@ v "../a") (v "../a")
equal (rem_empty_seg @@ v "../../a") (v "../../a")
equal (rem_empty_seg @@ v "\\\\server\\share\\")
(v "\\\\server\\share\\")
(Windows)equal (rem_empty_seg @@ v "C:\\") (v "C:\\")
(Windows)equal (rem_empty_seg @@ v "C:a\\") (v "C:a")
(Windows)equal (normalize @@ v ".") (v "./")
equal (normalize @@ v "..") (v "../")
equal (normalize @@ v "../") (v "../")
equal (normalize @@ v "../../") (v "../../")
equal (normalize @@ v "/") (v "/")
equal (normalize @@ v "/a/b/") (v "/a/b/")
equal (normalize @@ v "/a/b") (v "/a/b")
equal (normalize @@ v "a/") (v "a/")
equal (normalize @@ v "a") (v "a")
equal (normalize @@ v "a/.") (v "a/")
equal (normalize @@ v "a/./") (v "a/")
equal (normalize @@ v "a/..") (v "./")
equal (normalize @@ v "a/../") (v "./")
equal (normalize @@ v "a/..b") (v "a/..b")
equal (normalize @@ v "./a") (v "a")
equal (normalize @@ v "../a") (v "../a")
equal (normalize @@ v "../../a") (v "../../a")
equal (normalize @@ v "./a/..") (v "./")
equal (normalize @@ v "/a/b/./..") (v "/a/")
equal (normalize @@ v "/../..") (v "/")
equal (normalize @@ v "/a/../..") (v "/")
equal (normalize @@ v "./../..") (v "../../")
equal (normalize @@ v "../../a/") (v "../../a/")
equal (normalize @@ v "/a/b/c/./../../g") (v "/a/g")
equal (normalize @@ v "/a/b/c/./../../g/") (v "/a/g/")
equal (normalize @@ v "\\\\?\\UNC\\server\\share\\..")
(v "\\\\?\\UNC\\server\\share\\")
(Windows)equal (normalize @@ v "\\\\server\\share\\")
(v "\\\\server\\share\\")
(Windows)equal (normalize @@ v "C:\\") (v "C:\\")
(Windows)equal (normalize @@ v "C:a\\") (v "C:a\\")
(Windows)is_prefix (v "/a/b") (v "/a/b") = true
is_prefix (v "/a/b") (v "/a/bc") = false
is_prefix (v "/a/b") (v "/a/b/") = true
is_prefix (v "a/b/") (v "a/b") = false
is_prefix (v "a/b/") (v "a/b/") = true
is_prefix (v "a/b/") (v "a/b/c") = true
is_prefix (v ".") (v "./") = true
is_prefix (v "..") (v ".") = false
is_prefix (v "C:a") (v "a") = false
(Windows)find_prefix (v "a/b/c") (v "a/b/d")
is Some (v "a/b/")
find_prefix (v "a/b/c") (v "a/b/cd")
is Some (v "a/b/")
find_prefix (v "a/b") (v "a/b")
is Some (v "a/b")
find_prefix (v "a/b") (v "a/b/")
is Some (v "a/b")
find_prefix (v "a/b") (v "e/f")
is None
find_prefix (v "/a/b") (v "/e/f")
is Some (v "/")
find_prefix (v "/a/b") (v "e/f")
is None
find_prefix (v "C:\\a") (v "\\a")
is None
(Windows)rem_prefix (v "a/b/") (v "a/b")
is None
rem_prefix (v "a/b/") (v "a/b/")
is None
rem_prefix (v "a/b") (v "a/b")
is None
rem_prefix (v "a/b") (v "a/b/")
is Some "./"
rem_prefix (v "a/b") (v "a/b/c")
is Some (v "c")
rem_prefix (v "a/b/") (v "a/b/c")
is Some (v "c")
rem_prefix (v "a/b") (v "a/b/c/")
is Some (v "c/")
rem_prefix (v "a/b/") (v "a/b/c/")
is Some (v "c/")
rem_prefix (v "C:\\a") (v "C:\\a\\b")
is Some (v "b")
(Windows)relativize ~root:(v "/a/b") (v "c")
is None
relativize ~root:(v "/a/b") (v "/c")
is Some (v "../../c")
relativize ~root:(v "/a/b") (v "/c/")
is Some (v "../../c/")
relativize ~root:(v "/a/b") (v "/c")
is Some (v "../../c")
relativize ~root:(v "/a/b") (v "/c/")
is Some (v "../../c/")
relativize ~root:(v "/a/b") (v "/a/b/c")
is Some (v "c")
relativize ~root:(v "/a/b") (v "/a/b/c/")
is Some (v "c/")
relativize ~root:(v "/a/b") (v "/a/b")
is None
relativize ~root:(v "/a/b") (v "/a/b/")
is Some (v ".")
relativize ~root:(v "a/b") (v "/c")
is None
.relativize ~root:(v "a/b") (v "c")
is Some (v "../../c")
relativize ~root:(v "a/b") (v "c/")
is Some (v "../../c/")
relativize ~root:(v "a/b") (v "a/b/c")
is Some (v "c")
relativize ~root:(v "a/b") (v "a/b")
is Some (v ".")
relativize ~root:(v "a/b") (v "a/b/")
is Some (v ".")
relativize ~root:(v "../") (v "./")
is None
relativize ~root:(v "../a") (v "b")
is None
relativize ~root:(v "../a") (v "../b/c")
is Some (v "../b/c")
relativize ~root:(v "../../a") (v "../b")
is None
relativize ~root:(v "../a") (v "../../b")
is (Some "../../b")
is_rooted ~root:(v "a/b") (v "a/b") = false
is_rooted ~root:(v "a/b") (v "a/b/") = true
is_rooted ~root:(v "a/b/") (v "a/b") = false
is_rooted ~root:(v "a/b/") (v "a/b/") = true
is_rooted ~root:(v "./") (v "a") = true
is_rooted ~root:(v "./") (v "a/") = true
is_rooted ~root:(v "./") (v "a/../") = true
is_rooted ~root:(v "./") (v "..") = false
is_rooted ~root:(v "../") (v "./") = false
is_rooted ~root:(v "../") (v "a") = false
is_rooted ~root:(v "../") (v "../") = true
is_rooted ~root:(v "../") (v "../a") = true
is_rooted ~root:(v "../a") (v "./") = false
is_rooted ~root:(v "/a") (v "/a/..") = true
is_rooted ~root:(v "/a") (v "/a/../") = true
is_rooted ~root:(v "/a") (v "/..") = true
is_root (v "/") = true
is_root (v "/a") = false
is_root (v "/a/..") = false
is_root (v "//") = true
(POSIX)is_root (v "\\\\.\\dev\\") = true
(Windows)is_root (v "\\\\.\\dev\\a") = false
(Windows)is_root (v "\\\\server\\share\\") = true
(Windows)is_root (v "\\\\server\\share\\a") = false
(Windows)is_root (v "C:\\") = true
(Windows)is_root (v "C:a") = false
(Windows)is_root (v "C:\\a") = false
(Windows)get_ext (v "/") = ""
get_ext (v "a/b") = ""
get_ext (v "a/b.mli/..") = ""
get_ext (v "a/b.mli/...") = ""
get_ext (v "a/b.") = "."
get_ext (v "a/b.mli") = ".mli"
get_ext ~multi:true (v "a/b.mli") = ".mli"
get_ext (v "a/b.mli/") = ".mli"
get_ext (v "a/.ocamlinit") = ""
get_ext (v "a/.emacs.d") = ".d"
get_ext (v "a/.emacs.d/") = ".d"
get_ext ~multi:true (v "a/.emacs.d") = ".d"
get_ext (v "a.tar.gz") = ".gz"
get_ext ~multi:true (v "a.tar.gz") = ".tar.gz"
has_ext "mli" (v "a/b.mli") = true
has_ext ".mli" (v "a/b.mli") = true
has_ext ".mli" (v "a/b.mli/") = true
has_ext ".mli" (v "a/bmli") = false
has_ext "mli" (v "a/bmli") = false
has_ext ".tar.gz" (v "a/f.tar.gz") = true
has_ext "tar.gz" (v "a/f.tar.gz") = true
has_ext ".gz" (v "a/f.tar.gz") = true
has_ext ".tar" (v "a/f.tar.gz") = false
has_ext ".cache" (v "a/.cache") = false
has_ext "" (v "a/b") = false
has_ext "" (v "a/b.") = true
has_ext "." (v "a/b.") = true
exists_ext (v "a/f") = false
exists_ext (v "a/f.") = true
exists_ext (v "a/f.gz") = true
exists_ext ~multi:true (v "a/f.gz") = false
exists_ext (v "a/f.tar.gz") = true
exists_ext ~multi:true (v "a/f.tar.gz") = true
exists_ext (v "a/f.tar.gz/") = true
exists_ext (v ".emacs.d") = true
exists_ext (v ".emacs.d/") = true
exists_ext (v ".ocamlinit") = false
equal (add_ext "mli" (v "a/b")) (v "a/b.mli")
equal (add_ext ".mli" (v "a/b")) (v "a/b.mli")
equal (add_ext ".mli" (v "a/b/")) (v "a/b.mli/")
equal (add_ext ".mli" (v "/")) (v "/")
equal (add_ext ".mli" (v "a/b/..")) (v "a/b/..")
equal (add_ext "." (v "a/b")) (v "a/b.")
equal (add_ext "" (v "a/b")) (v "a/b")
equal (add_ext "tar.gz" (v "a/f")) (v "a/f.tar.gz")
equal (add_ext ".tar.gz" (v "a/f")) (v "a/f.tar.gz")
equal (add_ext "gz" (v "a/f.tar") ) (v "a/f.tar.gz")
equal (add_ext ".gz" (v "a/f.tar") ) (v "a/f.tar.gz")
equal (rem_ext @@ v "/") (v "/")
equal (rem_ext @@ v "/a/b") (v "/a/b")
equal (rem_ext @@ v "/a/b.mli") (v "/a/b")
equal (rem_ext @@ v "/a/b.mli/") (v "/a/b/")
equal (rem_ext @@ v "/a/b.mli/..") (v "/a/b.mli/..")
equal (rem_ext @@ v "/a/b.mli/.") (v "/a/b.mli/.")
equal (rem_ext @@ v "a/.ocamlinit") (v "a/.ocamlinit")
equal (rem_ext @@ v "a/.emacs.d") (v "a/.emacs")
equal (rem_ext @@ v "a/.emacs.d/") (v "a/.emacs/")
equal (rem_ext @@ v "f.tar.gz") (v "f.tar")
equal (rem_ext ~multi:true @@ v "f.tar.gz") (v "f")
equal (rem_ext ~multi:true @@ v "f.tar.gz/") (v "f/")