Next: , Previous: digraph, Up: Top   [Contents]


22 dir

%--------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%--------------------------------------------------%
% Copyright (C) 1994-1995,1997,1999-2000,2002-2012 The University of Melbourne.
% Copyright (C) 2016-2023 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%--------------------------------------------------%
%
% File: dir.m.
% Main authors: fjh, stayl.
% Stability: high.
%
% Filename and directory handling.
%
% Note that the predicates and functions in this module change directory
% separators in paths passed to them to the normal separator for the platform,
% if that does not change the meaning of the path name.
%
% Duplicate directory separators and trailing separators are also removed
% where that does not change the meaning of the path name.
%
%--------------------------------------------------%
%--------------------------------------------------%

:- module dir.
:- interface.

:- import_module bool.
:- import_module io.
:- import_module list.

%--------------------------------------------------%
%
% Predicates to isolate system dependencies.
%

    % Return the default separator between components of a pathname,
    % which is '/' on Unix systems and '\\' on Microsoft Windows systems.
    %
:- func directory_separator = character.
:- pred directory_separator(character::out) is det.

    % Is the character a directory separator.
    % On Microsoft Windows systems, this will succeed for both '/' and '\\'.
    %
:- pred is_directory_separator(character).
:- mode is_directory_separator(in) is semidet.
:- mode is_directory_separator(out) is multi.

    % Returns ".".
    %
:- func this_directory = string.
:- pred this_directory(string::out) is det.

    % Returns "..".
    %
:- func parent_directory = string.
:- pred parent_directory(string::out) is det.

    % split_name(PathName, DirName, BaseName).
    %
    % Split a filename into a directory part and a filename part.
    %
    % Fails for root directories or relative filenames not containing
    % directory information.
    %
    % Trailing slashes are removed from PathName before splitting,
    % if that does not change the meaning of PathName.
    %
    % Trailing slashes are removed from DirName after splitting,
    % if that does not change the meaning of DirName.
    %
    % On Windows, drive current directories are handled correctly,
    % for example `split_name("C:foo", "C:", "foo")'.
    % (`X:' is the current directory on drive `X').
    % Note that Cygwin does not support drive current directories,
    % so `split_name("C:foo", _, _)' will fail when running under Cygwin.
    %
:- pred split_name(string::in, string::out, string::out) is semidet.

    % basename(PathName) = BaseName.
    %
    % Returns the non-directory part of a filename.
    %
    % Fails when given a root directory, ".", ".." or a Windows path
    % such as "X:".
    %
    % Trailing slashes are removed from PathName before splitting,
    % if that does not change the meaning of PathName.
    %
:- func basename(string) = string is semidet.
:- pred basename(string::in, string::out) is semidet.

    % As above, but throws an exception instead of failing.
    %
:- func det_basename(string) = string.

    % dirname(PathName) = DirName.
    %
    % Returns the directory part of a filename.
    %
    % Returns PathName if it specifies a root directory.
    %
    % Returns PathName for Windows paths such as "X:".
    %
    % Returns `this_directory' when given a filename
    % without any directory information (e.g. "foo").
    %
    % Trailing slashes in PathName are removed first, if that does not change
    % the meaning of PathName.
    %
    % Trailing slashes are removed from DirName after splitting,
    % if that does not change the meaning of DirName.
    %
:- func dirname(string) = string.
:- pred dirname(string::in, string::out) is det.

    % path_name_is_root_directory(PathName)
    %
    % On Unix, '/' is the only root directory.
    % On Windows, a root directory is one of the following:
    %   'X:\', which specifies the root directory of drive X,
    %       where X is any letter.
    %   '\', which specifies the root directory of the current drive.
    %   '\\server\share\', which specifies a UNC (Universal Naming Convention)
    %       root directory for a network drive.
    %
    % Note that 'X:' is not a Windows root directory -- it specifies the
    % current directory on drive X, where X is any letter.
    %
:- pred path_name_is_root_directory(string::in) is semidet.

    % path_name_is_absolute(PathName)
    %
    % Is the path name syntactically an absolute path
    % (this does not check whether the path exists).
    %
    % A path is absolute iff it begins with a root directory
    % (see path_name_is_root_directory).
    %
:- pred path_name_is_absolute(string::in) is semidet.

    % PathName = DirName / FileName
    %
    % Given a directory name and a filename, return the pathname of that
    % file in that directory.
    %
    % Duplicate directory separators will not be introduced if
    % DirName ends with a directory separator.
    %
    % On Windows, a call such as `"C:"/"foo"' will return "C:foo".
    %
    % Throws an exception if FileName is an absolute path name.
    % Throws an exception on Windows if FileName is a current
    % drive relative path such as "C:".
    %
:- func string / string = string.
:- func make_path_name(string, string) = string.

    % relative_path_name_from_components(List) = PathName.
    %
    % Return the relative pathname from the components in the list.
    % The components of the list must not contain directory separators.
    %
:- func relative_path_name_from_components(list(string)) = string.

%--------------------------------------------------%

    % current_directory(Result)
    % Return the current working directory.
    %
:- pred current_directory(io.res(string)::out, io::di, io::uo) is det.

%--------------------------------------------------%

    % Make the given directory, and all parent directories.
    % This will also succeed if the directory already exists
    % and is readable and writable by the current user.
    %
:- pred make_directory(string::in, io.res::out, io::di, io::uo) is det.

    % Make only the given directory.
    % Fails if the directory already exists, or the parent directory
    % does not exist.
    %
:- pred make_single_directory(string::in, io.res::out, io::di, io::uo)
    is det.

%--------------------------------------------------%

    % FoldlPred(DirName, BaseName, FileType, Continue, !Data, !IO).
    %
    % A predicate passed to foldl2 to process each entry in a directory.
    % Processing will stop if Continue is bound to `no'.
    %
:- type foldl_pred(T) ==
    pred(string, string, io.file_type, bool, T, T, io, io).
:- inst foldl_pred == (pred(in, in, in, out, in, out, di, uo) is det).

    % foldl2(Pred, DirName, InitialData, Result, !IO):
    %
    % Apply Pred to all files and directories in the given directory.
    % Directories are not processed recursively.
    % Processing will stop if the boolean (Continue) output of Pred is bound
    % to `no'.
    % The order in which the entries are processed is unspecified.
    %
:- pred foldl2(foldl_pred(T)::in(foldl_pred), string::in,
    T::in, io.maybe_partial_res(T)::out, io::di, io::uo) is det.

    % recursive_foldl2(Pred, DirName, FollowSymLinks, InitialData, Result,
    %   !IO):
    %
    % As above, but recursively process subdirectories.
    % Subdirectories are processed depth-first, processing the directory itself
    % before its contents. If FollowSymLinks is `yes', recursively process
    % the directories referenced by symbolic links.
    %
:- pred recursive_foldl2(foldl_pred(T)::in(foldl_pred),
    string::in, bool::in, T::in, io.maybe_partial_res(T)::out,
    io::di, io::uo) is det.

:- type fold_params
    --->    fold_params(
                fp_subdirs      :: maybe_subdirs,
                fp_on_error     :: on_error
            ).

:- type maybe_subdirs
    --->    do_not_enter_subdirs
    ;       enter_subdirs(maybe_follow_symlinks).

:- type maybe_follow_symlinks
    --->    do_not_follow_symlinks
    ;       follow_symlinks.

:- type on_error
    --->    on_error_stop
    ;       on_error_keep_going.

:- type file_error
    --->    file_error(string, file_operation, io.error).
            % file_error(PathName, Operation, Error) means that
            % when we tried to perform Operation on PathName, the result
            % was Error. PathName specifies the file name relative to
            % the directory name given to general_foldl2.

:- type file_operation
    --->    file_open
    ;       file_close
    ;       file_get_id
    ;       file_get_type
    ;       file_check_accessibility
    ;       file_read_dir_entry.

    % general_foldl2(Params, Pred, DirName, Data0, Data, Errors, !IO).
    %
    % A generalised version of the above, whose behavior is controlled
    % by setting up Params.
    %
    % Whether we recursively process subdirectories depends on whether
    % the fp_subdirs field of Params is do_not_enter_subdirs or enter_subdirs.
    % If it is do_not_enter_subdirs, then we do not process subdirectories
    % at all. If it is enter_subdirs, then we process subdirectories depth
    % first. The traversal is preorder, meaning that we call Pred on the
    % pathname of a subdirectory *before* we process the contents of that
    % subdirectory.
    %
    % Whether we recursively process subdirectories referenced by symlinks
    % depends on the first argument of enter_subdirs.
    %
    % When we encounter an error, such as a failure to open a directory
    % for reading, we record that error, but what happens after that
    % depends on the fp_on_error field of Params. If this field is
    % on_error_stop, then we stop the traversal, which means that
    % with on_error_stop, we will return at most one error.
    % If it is on_error_keep_going, we continue with the traversal after
    % errors, which means that with on_error_keep_going, we can return
    % more than one error.
    %
    % Regardless of the setting of fp_on_error, we stop the traversal
    % if Pred returns Continue = `no'.
    %
    % In all cases, the value of Data will reflect the results of all
    % the invocations of Pred during the traversal up to the time
    % the traversal was stopped either by an error, by Continue = `no',
    % or by running out of files to traverse.
    %
:- pred general_foldl2(fold_params::in, foldl_pred(T)::in(foldl_pred),
    string::in, T::in, T::out, list(file_error)::out,
    io::di, io::uo) is det.

%--------------------------------------------------%

    % Implement brace expansion, as in sh: return the sequence of strings
    % generated from the given input string. Throw an exception if the
    % input string contains mismatched braces.
    %
    % The following is the documentation of brace expansion from the sh manual:
    %
    %   Brace expansion is a mechanism by which arbitrary strings may be
    %   generated. This mechanism is similar to pathname expansion, but the
    %   filenames generated need not exist. Patterns to be brace expanded
    %   take the form of an optional preamble, followed by a series of
    %   comma-separated strings between a pair of braces, followed by an
    %   optional postscript. The preamble is prefixed to each string contained
    %   within the braces, and the postscript is then appended to each
    %   resulting string, expanding left to right.
    %
    %   Brace expansions may be nested. The results of each expanded string
    %   are not sorted; left to right order is preserved. For example,
    %   a{d,c,b}e expands into `ade ace abe'.
    %
:- func expand_braces(string) = list(string).

%--------------------------------------------------%
%--------------------------------------------------%


Next: , Previous: digraph, Up: Top   [Contents]