%--------------------------------------------------% % 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. % % Returns the default separator between components of a pathname -- % '/' 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 '/' % as well as '\\'. % :- 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). %--------------------------------------------------% %--------------------------------------------------%