File path names

Author(s): Jose F. Morales.

A pathname is an symbolic identifier that locates a file in a filesystem (e.g., foo/bar/baz.txt). This module provides predicates to manipulate pathnames, encoded as atoms. No file system access is required for pathname manipulation.

Documentation on exports

REGTYPE

Usage:pathname(X)

X is a pathname (encoded as an atom)

    PREDICATE

    Usage:path_is_absolute(Path)

    Path is an absolute pathname

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_is_relative(Path)

    Path is a relative pathname

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_is_basename(Path)

    Path is a basename (empty directory part) (equivalent to path_split(Path, ”, _)

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_is_root(Path)

    Path is a root directory (normalized into / or //) (equivalent to + Path = ”, path_split(Path, Path, ”)

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_concat(PathA,PathB,Path)

    Concatenate pathnames PathA and PathB in a new path Path, adding a / separator if needed. If PathB is , then Path is a / ended path. If PathB is an absolute pathname, Path is PathB. No pathname normalization is performed in any case.

    • Call and exit should be compatible with:
      (pathnames:pathname/1)PathA is a pathname (encoded as an atom)
      (pathnames:pathname/1)PathB is a pathname (encoded as an atom)
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)PathA is currently a term which is not a free variable.
      (term_typing:nonvar/1)PathB is currently a term which is not a free variable.

    PREDICATE
    path_split(Path,Dir,Base)

    Given a pathname Path, Base is the last component of the path and Dir is the rest of the path. The following rules must hold:

    • All trailing slashes from Dir are removed (except if it is a root directory containing one or more slashes, e.g., '/', '//').
    • If Path ends in a slash ('.../', Base is empty ().
    • If Path is empty, both Dir and Base are empty.
    • Concatenating Dir and Base results in pathname that is equivalent (modulo normalization) to Path. That is, for all A, B, C:
              path_split(A,B,C),
              path_concat(B,C,D),
              path_norm(A,An), path_norm(D,Dn), An = Dn.
            

    Usage:path_split(Path,Dir,Base)

    Split Path into the directory part Dir and the basename part Base.

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)Dir is a pathname (encoded as an atom)
      (pathnames:pathname/1)Base is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.
      (term_typing:nonvar/1)Dir is currently a term which is not a free variable.

    PREDICATE
    path_norm(Path,NormPath)

    NormPath is the normalized pathname version of Path. Normalization removes redundant separators (// into /), and collapses references to the parent (..) and current (.) level. The parent of a root path (/ or //) is itself. Empty paths () or relative paths where all parent references are collapsed are normalized as '.'.

    For compatibility, leading double-slashes are preserved in normalization (the POSIX.2 standard states that "a pathname that begins with two successive slashes may be interpreted in an implementation-defined manner, although more than two leading slashes shall be treated as a single slash."). Some systems (like Linux) ignore double-slashes while others (like Cygwin for //hostname/path SMB network drives) do not.

    Note that path_norm/2 does not access the filesystem, which may affect the semantics when symbolic links are used (and no path normalization is involved). E.g., the following query obtain different values for S1 and S2:

    ?- P = '/usr/donotexists/..',
       path_norm(P, N),
       process_call(path(test), ['-e', P], [status(S1)]),
       process_call(path(test), ['-e', N], [status(S2)]).
    
    N = '/usr',
    P = '/usr/donotexists/..',
    S1 = 1,
    S2 = 0 ? 
    
    ?- use_module(library(process)).
    ?- copy_file('/usr', 'u', [symlink]),
       P = 'u/../u',
       path_norm(P, N),
       process_call(path(test), ['-e', P], [status(S1)]),
       process_call(path(test), ['-e', N], [status(S2)]),
       delete_file('u').
    
    N = u,
    P = 'u/../u',
    S1 = 1,
    S2 = 0 ? 
    
    yes
    

    Usage:path_norm(Path,NormPath)

    NormPath is the normalized pathname version of Path.

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)NormPath is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE
    path_splitext(Path,NoExt,Ext)

    The extension Ext is the shortest suffix that begins with '.' of Path. The rest of the pathname is NoExt, which cannot be empty (). The extension is if the pathname has no extension.

    In this example, all the following goals succeed:

    path_splitext('a/foo.', 'a/foo', '.')
    path_splitext('a/foo.c', 'a/foo', '.c')
    path_splitext('a/foo.c.d', 'a/foo.c', '.d')
    path_splitext('a/.foo.', 'a/.foo', '.')
    path_splitext('a/.foo.c', 'a/.foo', '.c')
    path_splitext('a/.foo.c.d', 'a/.foo.c', '.d')
    

    Usage 1:path_splitext(Path,NoExt,Ext)

    Split Path into its extension Ext and the rest of the pathname NoExt.

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)NoExt is a pathname (encoded as an atom)
      (pathnames:pathname/1)Ext is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    Usage 2:path_splitext(Path,NoExt,Ext)

    Compose Path by concatenating the extension Ext to NoExt pathname.

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)NoExt is a pathname (encoded as an atom)
      (pathnames:pathname/1)Ext is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)NoExt is currently a term which is not a free variable.
      (term_typing:nonvar/1)Ext is currently a term which is not a free variable.

    PREDICATE

    Usage:path_basename(Path,Base)

    Base is the basename corresponding to the Path (equivalent to path_split(Path,_,Base)).

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)Base is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_dirname(Path,Dir)

    Dir is the directory part corresponding to the Path (equivalent to path_split(Path,Dir,_)).

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)Dir is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_relocate(FromDir,ToDir,FromPath,ToPath)

    Replace FromDir prefix by DestDir in FromDir to generate ToDir

    • Call and exit should be compatible with:
      (pathnames:pathname/1)FromDir is a pathname (encoded as an atom)
      (pathnames:pathname/1)ToDir is a pathname (encoded as an atom)
      (pathnames:pathname/1)FromPath is a pathname (encoded as an atom)
      (pathnames:pathname/1)ToPath is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)FromDir is currently a term which is not a free variable.
      (term_typing:nonvar/1)ToDir is currently a term which is not a free variable.
      (term_typing:nonvar/1)FromPath is currently a term which is not a free variable.
      (term_typing:var/1)ToPath is a free variable.

    PREDICATE

    Usage:path_get_relative(BaseDir,Path,RelPath)

    Obtain path RelPath such that path_concat(BaseDir, RelPath, Path) RelPath will not contain any trailing '/'

    • Call and exit should be compatible with:
      (pathnames:pathname/1)BaseDir is a pathname (encoded as an atom)
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (pathnames:pathname/1)RelPath is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)BaseDir is currently a term which is not a free variable.
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.
      (term_typing:var/1)RelPath is a free variable.

    PREDICATE

    Usage:path_split_list(Path,Bases)

    Split Path into its components Bases, calling path_split/3 recursively.

    • Call and exit should be compatible with:
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
      (basic_props:list/2)Bases is a list of pathnames.
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Path is currently a term which is not a free variable.

    PREDICATE

    Usage:path_concat_list(Bases,Path)

    Concatenate all components in Bases a new path Path, calling path_concat/3 recursively.

    • Call and exit should be compatible with:
      (basic_props:list/2)Bases is a list of pathnames.
      (pathnames:pathname/1)Path is a pathname (encoded as an atom)
    • The following properties should hold at call time:
      (term_typing:nonvar/1)Bases is currently a term which is not a free variable.

    Known bugs and planned improvements

    • Some system predicates do unexpected path normalization (which also changes the semantics when symbolic links are used). Document at least.
    • path_norm/2 and expand_file_name in os_utils.c should be equivalent (disabling expansion of home and env vars)
    • Windows paths (drive letters) are not supported (see core/bugs/Pending/expand_file_trailing_slash/test.pl for some test cases)
    • This library offers no reversible versions of many of its predicates (e.g., path_concat/3) due to nondeterminism of reverse normalization. E.g., path_concat(a,B,'a/b'),B='b///' would fail unless all unnormalized paths are enumerated by the first call.

      Reversible path operations can be implemented by decomposing paths in list of names (using path_split/2 recursively), manipulating the lists, and composing back the paths (using path_concat/2 recursively).