-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdirPlus.m
318 lines (259 loc) · 11.7 KB
/
dirPlus.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
function output = dirPlus(rootPath, varargin)
%dirPlus Recursively collect files or directories within a folder.
% LIST = dirPlus(ROOTPATH) will search recursively through the folder
% tree beneath ROOTPATH and collect a cell array LIST of all files it
% finds. The list will contain the absolute paths to each file starting
% at ROOTPATH.
%
% LIST = dirPlus(ROOTPATH, 'PropertyName', PropertyValue, ...) will
% modify how files and directories are selected, as well as the format of
% LIST, based on the property/value pairs specified. Valid properties
% that the user can set are:
%
% GENERAL:
% 'Struct' - A logical value determining if the output LIST should
% instead be a structure array of the form returned by
% the DIR function. If TRUE, LIST will be an N-by-1
% structure array instead of a cell array.
% 'Depth' - A non-negative integer value for the maximum folder
% tree depth that dirPlus will search through. A value
% of 0 will only search in ROOTPATH, a value of 1 will
% search in ROOTPATH and its subfolders, etc. Default
% (and maximum allowable) value is the current
% recursion limit set on the root object (i.e.
% get(0, 'RecursionLimit')).
% 'ReturnDirs' - A logical value determining if the output will be a
% list of files or subdirectories. If TRUE, LIST will
% be a cell array of subdirectory names/paths. Default
% is FALSE.
% 'PrependPath' - A logical value determining if the full path from
% ROOTPATH to the file/subdirectory is prepended to
% each item in LIST. The default TRUE will prepend the
% full path, otherwise just the file/subdirectory name
% is returned. This setting is ignored if the 'Struct'
% argument is TRUE.
%
% FILE-SPECIFIC:
% 'FileFilter' - A string defining a regular-expression pattern
% that will be applied to the file name. Only files
% matching the pattern will be included in LIST.
% Default is '' (i.e. all files are included).
% 'ValidateFileFcn' - A handle to a function that takes as input a
% structure of the form returned by the DIR
% function and returns a logical value. This
% function will be applied to all files found and
% only files that have a TRUE return value will be
% included in LIST. Default is [] (i.e. all files
% are included).
%
% DIRECTORY-SPECIFIC:
% 'DirFilter' - A string defining a regular-expression pattern
% that will be applied to the subdirectory name.
% Only subdirectories matching the pattern will be
% considered valid (i.e. included in LIST themselves
% or having their files included in LIST). Default
% is '' (i.e. all subdirectories are valid). The
% setting of the 'RecurseInvalid' argument
% determines if invalid subdirectories are still
% recursed down.
% 'ValidateDirFcn' - A handle to a function that takes as input a
% structure of the form returned by the DIR function
% and returns a logical value. This function will be
% applied to all subdirectories found and only
% subdirectories that have a TRUE return value will
% be considered valid (i.e. included in LIST
% themselves or having their files included in
% LIST). Default is [] (i.e. all subdirectories are
% valid). The setting of the 'RecurseInvalid'
% argument determines if invalid subdirectories are
% still recursed down.
% 'RecurseInvalid' - A logical value determining if invalid
% subdirectories (as identified by the 'DirFilter'
% and 'ValidateDirFcn' arguments) should still be
% recursed down. Default is FALSE (i.e the recursive
% searching stops at invalid subdirectories).
%
% Examples:
%
% 1) Find all '.m' files:
%
% fileList = dirPlus(rootPath, 'FileFilter', '\.m$');
%
% 2) Find all '.m' files, returning the list as a structure array:
%
% fileList = dirPlus(rootPath, 'Struct', true, ...
% 'FileFilter', '\.m$');
%
% 3) Find all '.jpg', '.png', and '.tif' files:
%
% fileList = dirPlus(rootPath, 'FileFilter', '\.(jpg|png|tif)$');
%
% 4) Find all '.m' files in the given folder and its subfolders:
%
% fileList = dirPlus(rootPath, 'Depth', 1, 'FileFilter', '\.m$');
%
% 5) Find all '.m' files, returning only the file names:
%
% fileList = dirPlus(rootPath, 'FileFilter', '\.m$', ...
% 'PrependPath', false);
%
% 6) Find all '.jpg' files with a size of more than 1MB:
%
% bigFcn = @(s) (s.bytes > 1024^2);
% fileList = dirPlus(rootPath, 'FileFilter', '\.jpg$', ...
% 'ValidateFcn', bigFcn);
%
% 7) Find all '.m' files contained in folders containing the string
% 'addons', recursing without restriction:
%
% fileList = dirPlus(rootPath, 'DirFilter', 'addons', ...
% 'FileFilter', '\.m$', ...
% 'RecurseInvalid', true);
%
% See also dir, regexp.
% Author: Ken Eaton
% Version: MATLAB R2016b
% Last modified: 3/21/17
% Copyright 2017 by Kenneth P. Eaton
%--------------------------------------------------------------------------
% Create input parser (only have to do this once, hence the use of a
% persistent variable):
persistent parser
if isempty(parser)
recursionLimit = get(0, 'RecursionLimit');
parser = inputParser();
parser.FunctionName = 'dirPlus';
parser.PartialMatching = true;
% Add general parameters:
addRequired(parser, 'rootPath', ...
@(s) validateattributes(s, {'char'}, {'nonempty'}));
addParameter(parser, 'Struct', false, ...
@(b) validateattributes(b, {'logical'}, {'scalar'}));
addParameter(parser, 'Depth', recursionLimit, ...
@(s) validateattributes(s, {'numeric'}, ...
{'scalar', 'nonnegative', ...
'nonnan', 'integer', ...
'<=', recursionLimit}));
addParameter(parser, 'ReturnDirs', false, ...
@(b) validateattributes(b, {'logical'}, {'scalar'}));
addParameter(parser, 'PrependPath', true, ...
@(b) validateattributes(b, {'logical'}, {'scalar'}));
% Add file-specific parameters:
addParameter(parser, 'FileFilter', '', ...
@(s) validateattributes(s, {'char'}, {'row'}));
addParameter(parser, 'ValidateFileFcn', [], ...
@(f) validateattributes(f, {'function_handle'}, ...
{'scalar'}));
% Add directory-specific parameters:
addParameter(parser, 'DirFilter', '', ...
@(s) validateattributes(s, {'char'}, {'row'}));
addParameter(parser, 'ValidateDirFcn', [], ...
@(f) validateattributes(f, {'function_handle'}, ...
{'scalar'}));
addParameter(parser, 'RecurseInvalid', false, ...
@(b) validateattributes(b, {'logical'}, {'scalar'}));
end
% Parse input and recursively find contents:
parse(parser, rootPath, varargin{:});
output = dirPlus_core(parser.Results.rootPath, ...
rmfield(parser.Results, 'rootPath'), 0, true);
if parser.Results.Struct
output = vertcat(output{:});
end
end
%~~~Begin local functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%--------------------------------------------------------------------------
% Core recursive function to find files and directories.
function output = dirPlus_core(rootPath, optionStruct, depth, isValid)
% Get current directory contents:
rootData = dir(rootPath);
dirIndex = [rootData.isdir];
subDirs = {};
validIndex = [];
% Find valid subdirectories, only if necessary:
if (depth < optionStruct.Depth) || optionStruct.ReturnDirs
% Get subdirectories, not counting current or parent:
dirData = rootData(dirIndex);
subDirs = {dirData.name}.';
index = ~ismember(subDirs, {'.', '..'});
dirData = dirData(index);
subDirs = subDirs(index);
validIndex = true(size(subDirs));
if any(validIndex)
% Apply directory name filter, if specified:
nameFilter = optionStruct.DirFilter;
if ~isempty(nameFilter)
validIndex = ~cellfun(@isempty, regexp(subDirs, nameFilter));
end
if any(validIndex)
% Apply validation function to the directory list, if specified:
validateFcn = optionStruct.ValidateDirFcn;
if ~isempty(validateFcn)
validIndex(validIndex) = arrayfun(validateFcn, ...
dirData(validIndex));
end
end
end
end
% Determine if files or subdirectories are being returned:
if optionStruct.ReturnDirs % Return directories
% Use structure data or prepend full path, if specified:
if optionStruct.Struct
output = {dirData(validIndex)};
elseif any(validIndex) && optionStruct.PrependPath
output = fullfile(rootPath, subDirs(validIndex));
else
output = subDirs(validIndex);
end
elseif isValid % Return files
% Find all files in the current directory:
fileData = rootData(~dirIndex);
output = {fileData.name}.';
if ~isempty(output)
% Apply file name filter, if specified:
fileFilter = optionStruct.FileFilter;
if ~isempty(fileFilter)
filterIndex = ~cellfun(@isempty, regexp(output, fileFilter));
fileData = fileData(filterIndex);
output = output(filterIndex);
end
if ~isempty(output)
% Apply validation function to the file list, if specified:
validateFcn = optionStruct.ValidateFileFcn;
if ~isempty(validateFcn)
validateIndex = arrayfun(validateFcn, fileData);
fileData = fileData(validateIndex);
output = output(validateIndex);
end
% Use structure data or prepend full path, if specified:
if optionStruct.Struct
output = {fileData};
elseif ~isempty(output) && optionStruct.PrependPath
output = fullfile(rootPath, output);
end
end
end
else % Return nothing
output = {};
end
% Check recursion depth:
if (depth < optionStruct.Depth)
% Select subdirectories to recurse down:
if ~optionStruct.RecurseInvalid
subDirs = subDirs(validIndex);
validIndex = validIndex(validIndex);
end
% Recursively collect output from subdirectories:
nSubDirs = numel(subDirs);
if (nSubDirs > 0)
subDirs = fullfile(rootPath, subDirs);
output = {output; cell(nSubDirs, 1)};
for iSub = 1:nSubDirs
output{iSub+1} = dirPlus_core(subDirs{iSub}, optionStruct, ...
depth+1, validIndex(iSub));
end
output = vertcat(output{:});
end
end
end
%~~~End local functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~