OS.Path
structureThe OS.Path structure provides support for manipulating the syntax of file system paths independent of the underlying file system. It is purposely designed not to rely on any file system operations: none of the functions accesses the actual file system. There are two good reasons for this. Many systems support multiple file systems that may have different semantics, and applications may need to manipulate paths that do not exist in the underlying file system.
Before discussing the model and the semantics of the individual operations, we need to define some terms:
#"/"
in Unix and #"\\"
in DOS. For example, in Unix, the path "abc/def"
contains two arcs: "abc"
and "def"
. There are two special arcs: parentArc and currentArc. Under Unix and DOS, the parentArc and currentArc correspond to the strings ".."
and "."
, respectively. An empty arc corresponds to an empty string.
"/"
and "/a/b"
; DOS examples include "\"
, "\a\b"
and "A:\a\b"
.
".."
and "a/b"
; DOS examples include ".."
, "a\b"
and "A:a\b"
.
"." "/." "/" "a" "a/b/c" ".." "../a" "../../a/b/c" "/a/b/c"
""
. Under DOS, example volume names are ""
, "A:"
and "C:"
.
In addition to operations for canonicalizing paths and computing relative paths, the Path structure supports path manipulations relative to three different views of a path:
""
volume.
#"."
. This works for Windows, OS/2, and Unix; the Macintosh does not really have a notion of extension.
Our main design principle is that the functions should behave in a natural fashion when applied to canonical paths. All functions, except concat, preserve canonical paths, i.e., if all arguments are canonical, then so is the result.
Note that although the model of path manipulation provided by the Path structure is operating system independent, the analysis of strings is not. In particular, any given implementation of the Path structure has an implicit notion of what the arc separator character is. Thus, on a DOS system, Path will treat the string "\\d\\e"
as representing an absolute path with two arcs, whereas on a Unix system, it will correspond to a relative path with one arc.
signature OS_PATH
structure Path
: OS_PATH
exception Path
val parentArc : string
val currentArc : string
val validVolume : {isAbs : bool, vol : string} -> bool
val fromString : string -> {isAbs : bool, vol : string, arcs : string list}
val toString : {isAbs : bool, vol : string, arcs : string list} -> string
val getVolume : string -> string
val getParent : string -> string
val splitDirFile : string -> {dir : string, file : string}
val joinDirFile : {dir : string, file : string} -> string
val dir : string -> string
val file : string -> string
val splitBaseExt : string -> {base : string, ext : string option}
val joinBaseExt : {base : string, ext : string option} -> string
val base : string -> string
val ext : string -> string option
val mkCanonical : string -> string
val isCanonical : string -> bool
val mkAbsolute : (string * string) -> string
val mkRelative : (string * string) -> string
val isAbsolute : string -> bool
val isRelative : string -> bool
val isRoot : string -> bool
val concat : (string * string) -> string
exception Path
parentArc
".."
on DOS and UNIX).
currentArc
"."
on DOS and UNIX).
validVolume {isAbs, vol}
true
if vol is a valid volume name for an absolute or relative path, respectively as isAbs is true
or false
. Under Unix, the only valid volume name is ""
. Under DOS, the valid volume names have the form "a:"
, "A:"
, "b:"
, "B:"
, etc. and, if isAbs = false
, also ""
. Under MacOS, isAbs can be true
if and only if vol is ""
.
fromString s
{isAbs, vol, arcs}
of the path specified by s. vol is the volume name and arcs is the list of (possibly empty) arcs of the path. isAbs is true
if the path is absolute. Under Unix, the volume name is always the empty string; under DOS, in addition it can have the form "A:"
, "C:"
, etc.
Here are some examples for UNIX paths:
fromString "" = {isAbs=false, vol="", arcs=[]} fromString "/" = {isAbs=true, vol="", arcs=[""]} fromString "//" = {isAbs=true, vol="", arcs=["", ""]} fromString "a" = {isAbs=false, vol="", arcs=["a"]} fromString "/a" = {isAbs=true, vol="", arcs=["a"]} fromString "//a" = {isAbs=true, vol="", arcs=["","a"]} fromString "a/" = {isAbs=false, vol="", arcs=["a", ""]} fromString "a//" = {isAbs=false, vol="", arcs=["a", "", ""]} fromString "a/b" = {isAbs=false, vol="", arcs=["a", "b"]}
toString {isAbs, vol, arcs}
""
when applied to {isAbs=false, vol="", arcs=[]}
. The exception Path is raised if validVolume{isAbs, vol}
is false
, or if isAbs is false
and arcs has an initial empty arc.
toString o fromString
is the identity. fromString o toString
is also the identity, provided no exception is raised and none of the strings in arcs contains an embedded arc separator character. In addition, isRelative(toString {isAbs=false, vol, arcs})
evaluates to true when defined.
getVolume s
getParent s
getParent s = s
if and only if s is a root. If the last arc is empty or the parent arc, then getParent appends a parent arc. If the last arc is the current arc, then it is replaced with the parent arc. Note that if s is canonical, then the result of getParent will also be canonical.
Here are some examples for UNIX paths:
getParent "/" = "/" getParent "a" = "." getParent "a/" = "a/.." getParent "a///" = "a///.." getParent "a/b" = "a" getParent "a/b/" = "a/b/.." getParent ".." = "../.." getParent "." = ".." getParent "" = ".."
splitDirFile s
""
, if the last arc is ""
.
Here are some examples for UNIX paths:
splitDirFile "" = {dir = "", file = ""} splitDirFile "." = {dir = "", file = "."} splitDirFile "b" = {dir = "", file = "b"} splitDirFile "b/" = {dir = "b", file = ""} splitDirFile "a/b" = {dir = "a", file = "b"} splitDirFile "/a" = {dir = "/", file = "a"}
joinDirFile {dir, file}
dir s
file s
#dir o splitDirFile
and #file o splitDirFile
, respectively, although they are probably more efficient.
splitBaseExt s
"."
in the last arc; NONE is returned if the extension is not defined. The base part is everything to the left of the extension except the final "."
. Note that if there is no extension, a terminating "."
is included with the base part.
Here are some examples for UNIX paths:
splitBaseExt "" = {base = "", ext = NONE} splitBaseExt ".login" = {base = ".login", ext = NONE} splitBaseExt "/.login" = {base = "/.login", ext = NONE} splitBaseExt "a" = {base = "a", ext = NONE} splitBaseExt "a." = {base = "a.", ext = NONE} splitBaseExt "a.b" = {base = "a", ext = SOME "b"} splitBaseExt "a.b.c" = {base = "a.b", ext = SOME "c"} splitBaseExt ".news/comp" = {base = ".news/comp", ext = NONE}
joinBaseExt {base, ext}
joinBaseExt o splitBaseExt
is the identity. The opposite does not hold, since the extension may be empty, or may contain extension separators. Note that although splitBaseExt will never return the extension SOME ""
, joinBaseExt treats this as equivalent to NONE.
base s
ext s
#base o splitBaseExt
and #ext o splitBaseExt
, respectively, although they are probably more efficient.
mkCanonical s
"."
under Unix and DOS).
Note that the syntactic canonicalization provided by mkCanonical may not preserve file system meaning in the presence of symbolic links (cf. concat).
isCanonical s
(s = mkCanonical s)
.
mkAbsolute (p, abs)
mkCanonical (concat (abs, p))
Thus, if p and abs are canonical, the result will be canonical. If abs is not absolute, or if the two paths refer to different volumes, then the Path exception is raised.
mkRelative (p, abs)
If abs is not absolute, or if p and abs are both absolute but have different roots, the Path exception is raised.
Here are some examples for UNIX paths:
mkRelative ("a/b", "/c/d") = "a/b" mkRelative ("/", "/a/b/c") = "../../.." mkRelative ("/a/b/", "/a/c") = "../b/" mkRelative ("/a/b", "/a/c") = "../b" mkRelative ("/a/b/", "/a/c/") = "../b/" mkRelative ("/a/b", "/a/c/") = "../b" mkRelative ("/", "/") = "." mkRelative ("/", "/.") = "." mkRelative ("/", "/..") = "." mkRelative ("/a/b/../c", "/a/d") = "../b/../c" mkRelative ("/a/b", "/c/d") = "../../a/b" mkRelative ("/c/a/b", "/c/d") = "../a/b" mkRelative ("/c/d/a/b", "/c/d") = "a/b"
isAbsolute s
isRelative s
true
if s is, respectively, absolute or relative.
isRoot s
true
if s is a canonical specification of a root directory.
concat (s, t)
Note that concat does not preserve canonical paths. For example, concat("a/b", "../c")
returns "a/b/../c"
. The parent arc is not removed because "a/b/../c"
and "a/c"
may not be equivalent in the presence of symbolic links.
OS, OS.FileSys, OS.Process, OS.IO, Posix.FileSys
Last Modified June 14, 1996
Copyright © 1996 AT&T