-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnextname.m
134 lines (133 loc) · 4.75 KB
/
nextname.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
function [name,val] = nextname(bnm,sfx,ext,otp) %#ok<*ISMAT>
% Return the next unused filename, incrementing a numbered suffix if required.
%
% (c) 2017-2021 Stephen Cobeldick
%
%%% Syntax:
% name = nextname(bnm,sfx,ext)
% name = nextname(bnm,sfx,ext,otp)
%
% The base name is supplied as the first input. If the location to be
% checked is not the current directory then the base name must use a
% relative or absolute path to that location. The base name is treated
% as literal text. The second input is a suffix that will be
% appended to the base name: the suffix must contain exactly one starting
% integer, e.g.: '(0)', '_1', ' v005', '.temp.1000', etc., and NEXTNAME
% increments the integer value so that the returned name is currently
% unused by any file/folder. The third input is the file extension: for
% folders or if the file does not require an extension simply use ''.
%
% Padding the suffix integer with leading zeros indicates the required
% number length, thus allowing for the creation of fixed-width names.
%
% Duplicate number values will not be returned, regardless of any leading
% zeros in the existing file/folder names or in the requested suffix.
%
%% Examples %%
%
%%% Current directory contains files 'A1.m', 'A2.m', and 'A4.m':
%
% >> nextname('A','1','.m')
% ans = 'A3.m'
%
% >> nextname('A','001','.m')
% ans = 'A003.m'
%
%%% Subdirectory 'HTML' contains folders 'B(1)', 'B(2)', and 'B(4)':
%
% >> nextname('HTML\B','(1)','')
% ans = 'B(3)'
%
% >> nextname('HTML\B','(001)','')
% ans = 'B(003)'
%
% >> nextname('HTML\B','(1)','',false) % default = name only.
% ans = 'B(3)'
% >> nextname('HTML\B','(1)','',true) % prepend same path as the input name.
% ans = 'HTML\B(3)'
%
%% Inputs and Outputs %%
%
%%% Input Arguments (**==default):
% bnm = CharVector or StringScalar giving the base filename or folder name,
% with an absolute or relative file path if required.
% sfx = CharVector or StringScalar of the suffix to append, contains exactly
% one integer number (use leading zeros to specify the number of digits).
% ext = CharVector or StringScalar of the file extension, if any. For folder
% names or files without extensions use ''.
% otp = LogicalScalar, true/false** -> output with same path as input name / name only.
%
%%% Output Arguments:
% name = CharVector, a currently unused file/folder name.
% val = NumericScalar, the value used in the returned name.
%
% See also: DIR EXIST WHAT FILEPARTS FULLFILE MKDIR FOPEN SPRINTF NATSORTFILES ISDIR ISFOLDER
%% Input Wrangling %%
%
bnm = nn1s2c(bnm);
sfx = nn1s2c(sfx);
ext = nn1s2c(ext);
%
msg = 'Input <%s> must be a 1xN char vector or a scalar string.';
assert(ischar(bnm)&&ndims(bnm)==2&&size(bnm,1)==1,'SC.nextname:bnm:NotText',msg,'bnm')
assert(ischar(sfx)&&ndims(sfx)==2&&size(sfx,1)==1,'SC:nextname:sfx:NotText',msg,'sfx')
assert(ischar(ext)&&ndims(ext)==2&&size(ext,1)<=1,'SC:nextname:ext:NotText',msg,'ext')
%
tkn = regexp(sfx,'\d+','match');
val = sscanf(sprintf(' %s',tkn{:}),'%lu'); % faster than STR2DOUBLE.
assert(numel(val)==1,'SC:nextname:sfx:NotOneInteger',...
'The suffix must contain exactly one integer value (any number of digits).')
wid = numel(tkn{1});
%
%% Get Existing File / Folder Names %%
%
[inpth,fnm,tmp] = fileparts(bnm);
fnm = [fnm,tmp];
%
% Find files/subfolders on that path:
raw = dir(fullfile(inpth,[fnm,regexprep(sfx,'\d+','*'),ext]));
%
% Special case of exactly one matching subfolder (Octave):
if ~all(strncmp({raw.name},fnm,numel(fnm)))
raw = dir(fullfile(inpth,'*'));
end
%
% Generate regular expression:
rgx = regexprep(regexptranslate('escape',sfx),'\d+','(\\d+)');
rgx = ['^',regexptranslate('escape',fnm),rgx,regexptranslate('escape',ext),'$'];
%
% Extract numbers from names:
tkn = regexpi({raw.name},rgx,'tokens','once');
tkn = [tkn{:}];
%
%% Identify First Unused Name %%
%
if numel(tkn)
% For speed these values must be converted before the WHILE loop:
vec = sscanf(sprintf(' %s',tkn{:}),'%lu'); % faster than STR2DOUBLE.
%
% Find the first unused name, starting from the provided value:
while any(val==vec)
val = val+1;
end
end
%
name = [fnm,regexprep(sfx,'\d+',sprintf('%0*u',wid,val)),ext];
%
if nargin>3
assert(islogical(otp)&&isscalar(otp),'SC:nextname:otp:NotLogicalScalar',...
'Input <otp> must be a scalar logical (i.e. true or false).')
if otp
name = fullfile(inpth,name);
end
end
%
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%nextname
function arr = nn1s2c(arr)
% If scalar string then extract the character vector, otherwise data is unchanged.
if isa(arr,'string') && isscalar(arr)
arr = arr{1};
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%nn1s2c