1########################################################################
2##
3## Copyright (C) 2014-2021 The Octave Project Developers
4##
5## See the file COPYRIGHT.md in the top-level directory of this
6## distribution or <https://octave.org/copyright/>.
7##
8## This file is part of Octave.
9##
10## Octave is free software: you can redistribute it and/or modify it
11## under the terms of the GNU General Public License as published by
12## the Free Software Foundation, either version 3 of the License, or
13## (at your option) any later version.
14##
15## Octave is distributed in the hope that it will be useful, but
16## WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20## You should have received a copy of the GNU General Public License
21## along with Octave; see the file COPYING.  If not, see
22## <https://www.gnu.org/licenses/>.
23##
24########################################################################
25
26## -*- texinfo -*-
27## @deftypefn  {} {@var{filename} =} fullfile (@var{dir1}, @var{dir2}, @dots{}, @var{file})
28## @deftypefnx {} {@var{filenames} =} fullfile (@dots{}, @var{files})
29## Build complete filename from separate parts.
30##
31## Joins any number of path components intelligently.  The return value is
32## the concatenation of each component with exactly one file separator
33## between each non empty part and at most one leading and/or trailing file
34## separator.
35##
36## If the last component part is a cell array, returns a cell array of
37## filepaths, one for each element in the last component, e.g.:
38##
39## @example
40## @group
41## fullfile ("/home/username", "data", @{"f1.csv", "f2.csv", "f3.csv"@})
42##   @result{}
43##       @{
44##         [1,1] = /home/username/data/f1.csv
45##         [1,2] = /home/username/data/f2.csv
46##         [1,3] = /home/username/data/f3.csv
47##       @}
48## @end group
49## @end example
50##
51## On Windows systems, while forward slash file separators do work, they are
52## replaced by backslashes; in addition drive letters are stripped of leading
53## file separators to obtain a valid file path.
54##
55## Note: @code{fullfile} does not perform any validation of the resulting full
56## filename.
57## @seealso{fileparts, filesep}
58## @end deftypefn
59
60function filename = fullfile (varargin)
61
62  if (nargin && iscell (varargin{end}))
63    filename = cellfun (@(x) fullfile (varargin{1:end-1}, x), varargin{end},
64                                       "UniformOutput", false);
65  else
66    non_empty = cellfun ("isempty", varargin);
67    unc = 0;
68    if (ispc && ! isempty (varargin))
69      varargin = strrep (varargin, '/', filesep);
70      unc = strncmp (varargin{1}, '\\', 2);
71      varargin(1) = regexprep (varargin{1}, '[\\/]*([a-zA-Z]:[\\/]*)', "$1");
72    endif
73    filename = strjoin (varargin(! non_empty), filesep);
74    filename(unc + strfind (filename(1+unc : end), [filesep filesep])) = "";
75  endif
76
77endfunction
78
79
80%!shared fs, fsx, xfs, fsxfs, xfsy, xfsyfs
81%! fs = filesep ();
82%! fsx = [fs "x"];
83%! xfs = ["x" fs];
84%! fsxfs = [fs "x" fs];
85%! xfsy = ["x" fs "y"];
86%! xfsyfs = ["x" fs "y" fs];
87
88%!assert (fullfile (""), "")
89%!assert (fullfile (fs), fs)
90%!assert (fullfile ("", fs), fs)
91%!assert (fullfile (fs, ""), fs)
92%!assert (fullfile ("", fs), fs)
93%!assert (fullfile ("x"), "x")
94%!assert (fullfile ("", "x"), "x")
95%!assert (fullfile ("x", ""), "x")
96%!assert (fullfile ("", "x", ""), "x")
97%!assert (fullfile ("x", "y"), xfsy)
98%!assert (fullfile ("x", "", "y"), xfsy)
99%!assert (fullfile ("x", "", "y", ""), xfsy)
100%!assert (fullfile ("", "x", "", "y", ""), xfsy)
101%!assert (fullfile (fs), fs)
102%!assert (fullfile (fs, "x"), fsx)
103%!assert (fullfile (fs, xfs), fsxfs)
104%!assert (fullfile (fsx, fs), fsxfs)
105%!assert (fullfile (fs, "x", fs), fsxfs)
106
107%!assert (fullfile ("x/", "/", "/", "y", "/", "/"), xfsyfs)
108%!assert (fullfile ("/", "x/", "/", "/", "y", "/", "/"), [fs xfsyfs])
109%!assert (fullfile ("/x/", "/", "/", "y", "/", "/"), [fs xfsyfs])
110
111## different on purpose so that "fullfile (c{:})" works for empty c
112%!assert (fullfile (), "")
113
114%!assert (fullfile ("x", "y", {"c", "d"}), {[xfsyfs "c"], [xfsyfs "d"]})
115
116## Windows specific - drive letters and file sep type
117%!test
118%! if (ispc)
119%!   assert (fullfile ('\/\/\//A:/\/\', "x/", "/", "/", "y", "/", "/"), ...
120%!           ['A:\' xfsyfs]);
121%! endif
122
123## *nix specific - double backslash
124%!test
125%! if (isunix || ismac)
126%!   assert (fullfile (fs, fs), fs);
127%! endif
128
129## Windows specific - drive letters and file sep type, cell array
130%!test
131%! if (ispc)
132%!  tmp = fullfile ({"\\\/B:\//", "A://c", "\\\C:/g/h/i/j\/"});
133%!  assert (tmp{1}, 'B:\');
134%!  assert (tmp{2}, 'A:\c');
135%!  assert (tmp{3}, 'C:\g\h\i\j\');
136%! endif
137