Mercurial > octave
changeset 28259:467d0781130a
fullfile.m: Allow multiple cell arrays as input arg (bug #57774).
* scripts/miscellaneous/fullfile.m: Allow cell string input for any
number of arguments. Adapt documentation. Add BISTs for new feature.
Use "testif" for platform specific tests. Add BIST for UNC paths.
author | Markus Mützel <markus.muetzel@gmx.de> |
---|---|
date | Sun, 03 May 2020 16:16:17 +0200 |
parents | e9126e73748a |
children | 062f93e05658 |
files | scripts/miscellaneous/fullfile.m |
diffstat | 1 files changed, 92 insertions(+), 44 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/miscellaneous/fullfile.m Sat May 02 12:24:26 2020 -0700 +++ b/scripts/miscellaneous/fullfile.m Sun May 03 16:16:17 2020 +0200 @@ -25,16 +25,17 @@ ## -*- texinfo -*- ## @deftypefn {} {@var{filename} =} fullfile (@var{dir1}, @var{dir2}, @dots{}, @var{file}) -## @deftypefnx {} {@var{filenames} =} fullfile (@dots{}, @var{files}) ## Build complete filename from separate parts. ## -## Joins any number of path components intelligently. The return value is -## the concatenation of each component with exactly one file separator -## between each non empty part and at most one leading and/or trailing file +## The function joins any number of path components intelligently. The return +## value is the concatenation of each component with exactly one file separator +## between each part of the path and at most one leading and/or trailing file ## separator. ## -## If the last component part is a cell array, returns a cell array of -## filepaths, one for each element in the last component, e.g.: +## The input arguments might be strings or cell strings. Any input arguments +## that are cell strings must contain one single string or must be equal in +## size. In that case, the function returns a cell string of filepaths of the +## same dimensions as the input cell strings, e.g.: ## ## @example ## @group @@ -49,7 +50,7 @@ ## @end example ## ## On Windows systems, while forward slash file separators do work, they are -## replaced by backslashes; in addition drive letters are stripped of leading +## replaced by backslashes. In addition, drive letters are stripped of leading ## file separators to obtain a valid file path. ## ## Note: @code{fullfile} does not perform any validation of the resulting full @@ -59,19 +60,58 @@ function filename = fullfile (varargin) - if (nargin && iscell (varargin{end})) - filename = cellfun (@(x) fullfile (varargin{1:end-1}, x), varargin{end}, - "UniformOutput", false); - else - non_empty = cellfun ("isempty", varargin); - unc = 0; - if (ispc && ! isempty (varargin)) - varargin = strrep (varargin, '/', filesep); - unc = strncmp (varargin{1}, '\\', 2); - varargin(1) = regexprep (varargin{1}, '[\\/]*([a-zA-Z]:[\\/]*)', "$1"); - endif - filename = strjoin (varargin(! non_empty), filesep); - filename(unc + strfind (filename(1+unc : end), [filesep filesep])) = ""; + ## remove empty arguments + varargin(cellfun (@isempty, varargin)) = []; + + if (isempty (varargin)) + ## return early for all empty or no input + filename = ""; + return; + endif + + ## check input type + is_cellstr = cellfun (@iscellstr, varargin); + if (! all (is_cellstr | cellfun (@ischar, varargin))) + error ("fullfile: input must either be strings or cell strings"); + endif + + ## convert regular strings to cell strings + varargin(! is_cellstr) = num2cell (varargin(! is_cellstr)); + + ## check if input size matches + if (numel (varargin) > 1 && common_size (varargin{:}) != 0) + error ("fullfile: cell string input must be scalar or of the same size"); + endif + + fs = filesep (); + + if (ispc ()) + ## replace forward slashes with backslashes + varargin = cellfun (@(x) strrep (x, "/", fs), varargin, + "UniformOutput", false); + + ## Strip fileseps preceeding drive letters + varargin{1} = regexprep (varargin{1}, '\\*([a-zA-Z]:\\*)', "$1"); + + unc = strncmp (varargin{1}, '\\', 2); + endif + + ## insert file separator between elements + varargin(2,:) = {fs}; + varargin{end} = ""; + + filename = strcat (varargin{:}); + + ## remove multiplicate file separators + filename = regexprep (filename, [undo_string_escapes(fs), "*"], fs); + + if (ispc ()) + ## prepend removed file separator for UNC paths + filename(unc) = strcat (fs, filename(unc)); + endif + + if (! any (is_cellstr)) + filename = filename{1}; endif endfunction @@ -79,13 +119,14 @@ %!shared fs, fsx, xfs, fsxfs, xfsy, xfsyfs %! fs = filesep (); -%! fsx = [fs "x"]; -%! xfs = ["x" fs]; -%! fsxfs = [fs "x" fs]; -%! xfsy = ["x" fs "y"]; -%! xfsyfs = ["x" fs "y" fs]; +%! fsx = [fs, "x"]; +%! xfs = ["x", fs]; +%! fsxfs = [fs, "x", fs]; +%! xfsy = ["x", fs, "y"]; +%! xfsyfs = ["x", fs, "y", fs]; %!assert (fullfile (""), "") +%!assert (fullfile ("", ""), "") %!assert (fullfile (fs), fs) %!assert (fullfile ("", fs), fs) %!assert (fullfile (fs, ""), fs) @@ -105,32 +146,39 @@ %!assert (fullfile (fs, "x", fs), fsxfs) %!assert (fullfile ("x/", "/", "/", "y", "/", "/"), xfsyfs) -%!assert (fullfile ("/", "x/", "/", "/", "y", "/", "/"), [fs xfsyfs]) -%!assert (fullfile ("/x/", "/", "/", "y", "/", "/"), [fs xfsyfs]) +%!assert (fullfile ("/", "x/", "/", "/", "y", "/", "/"), [fs, xfsyfs]) +%!assert (fullfile ("/x/", "/", "/", "y", "/", "/"), [fs, xfsyfs]) ## different on purpose so that "fullfile (c{:})" works for empty c %!assert (fullfile (), "") -%!assert (fullfile ("x", "y", {"c", "d"}), {[xfsyfs "c"], [xfsyfs "d"]}) +%!assert (fullfile ("x", "y", {"c", "d"}), {[xfsyfs, "c"], [xfsyfs, "d"]}) +%!assert (fullfile ({"folder1", "folder2"}, "sub", {"f1.m", "f2.m"}), ... +%! {["folder1", fs, "sub", fs, "f1.m"], ... +%! ["folder2", fs, "sub", fs, "f2.m"]}); ## Windows specific - drive letters and file sep type -%!test -%! if (ispc) -%! assert (fullfile ('\/\/\//A:/\/\', "x/", "/", "/", "y", "/", "/"), ... -%! ['A:\' xfsyfs]); -%! endif +%!testif ; ispc () +%! assert (fullfile ('\/\/\//A:/\/\', "x/", "/", "/", "y", "/", "/"), ... +%! ['A:\' xfsyfs]); ## *nix specific - double backslash -%!test -%! if (isunix || ismac) -%! assert (fullfile (fs, fs), fs); -%! endif +%!testif ; ! ispc () +%! assert (fullfile (fs, fs), fs); + +## Windows specific - UNC path +%!testif ; ispc () +%! assert (fullfile ({'\/\//server1', 'C:', '\\server2\/'}, ... +%! "x/", "/", "/", "y", "/", "/"), ... +%! {['\\server1\', xfsyfs], ['C:\', xfsyfs], ['\\server2\', xfsyfs]}); ## Windows specific - drive letters and file sep type, cell array -%!test -%! if (ispc) -%! tmp = fullfile ({"\\\/B:\//", "A://c", "\\\C:/g/h/i/j\/"}); -%! assert (tmp{1}, 'B:\'); -%! assert (tmp{2}, 'A:\c'); -%! assert (tmp{3}, 'C:\g\h\i\j\'); -%! endif +%!testif ; ispc () +%! tmp = fullfile ({'\\\/B:\//', 'A://c', '\\\C:/g/h/i/j\/'}); +%! assert (tmp{1}, 'B:\'); +%! assert (tmp{2}, 'A:\c'); +%! assert (tmp{3}, 'C:\g\h\i\j\'); + +%!error <strings or cell strings> fullfile (1) +%!error <strings or cell strings> fullfile ({1}) +%!error <same size> fullfile ({"a", "b"}, {"a", "b", "c"})