Mercurial > hg > camir-aes2014
comparison toolboxes/graph_visualisation/graphViz4Matlab/util/processArgs.m @ 0:e9a9cd732c1e tip
first hg version after svn
author | wolffd |
---|---|
date | Tue, 10 Feb 2015 15:05:51 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e9a9cd732c1e |
---|---|
1 function varargout = processArgs(userArgs, varargin) | |
2 % Process function arguments, allowing args to be passed by | |
3 % the user either as name/value pairs, or positionally, or both. | |
4 % | |
5 % This function also provides optional enforcement of required inputs, and | |
6 % optional type checking. Argument names must start with the '-' | |
7 % character and not precede any positional arguments. | |
8 % | |
9 % Matthew Dunham | |
10 % University of British Columbia | |
11 % Last updated January 14th, 2010 | |
12 % | |
13 %% USAGE: | |
14 % | |
15 % [out1,out2,...,outN] = processArgs(userArgs ,... | |
16 % '-name1' , default1 ,... | |
17 % '-name2' , default2 ,... | |
18 % '-nameN' , defaultN ); | |
19 % | |
20 % The 'userArgs' input is a cell array and in normal usage is simply the | |
21 % varargin cell array from the calling function. It contains 0 to N values | |
22 % or 0 to N name/value pairs. It may also contain a combination of | |
23 % positional and named arguments as long as no named argument precedes a | |
24 % positional one. | |
25 % | |
26 % Note, this function is CASE INSENSITIVE. | |
27 % | |
28 %% ENFORCING REQUIRED ARGUMENTS | |
29 % | |
30 % To ensure that certain arguments are passed in, (and error if not), add a | |
31 % '*' character to the corresponding name as in | |
32 % | |
33 % [out1,...] = processArgs(userArgs,'*-name1',default1,... | |
34 % | |
35 % | |
36 % Providing empty values, (e.g. {},[],'') for required arguments also | |
37 % errors unless these were explicitly passed as named arguments. | |
38 %% TYPE CHECKING | |
39 % | |
40 % To enforce that the input type, (class) is the same type as the default | |
41 % value, add a '+' character to the corresponding name as in | |
42 % | |
43 % [out1,...] = processArgs(userArgs,'+-name1',default1,... | |
44 % | |
45 % '+' and '*' can be combined as in | |
46 % | |
47 % [out1,...] = processArgs(userArgs,'*+-name1',default1,... | |
48 % | |
49 % or equivalently | |
50 % | |
51 % [out1,...] = processArgs(userArgs,'+*-name1',default1,... | |
52 %% OTHER CONSIDERATIONS | |
53 % | |
54 % If the user passes in arguments in positional mode, and uses [], {}, or | |
55 % '' as place holders, the default values are used in their place. When the | |
56 % user passes in values via name/value pairs, this behavior does not | |
57 % occur; the explicit value the user specified, (even if [], {}, '') is | |
58 % always used. | |
59 % | |
60 %% ADVANCED | |
61 % The programmer must specify the same number of output arguments as | |
62 % possible input arguments, OR exactly one output. If exactly one output | |
63 % is used, the outputs are all bundled into a single cell array and | |
64 % returned with each arg value preceded by its name. This can be useful for | |
65 % relaying some or all of the arguments to subsequent functions as is done | |
66 % with the extractArgs function. | |
67 % | |
68 %% DEPENDENCIES | |
69 % | |
70 % None | |
71 % | |
72 %% EXAMPLES | |
73 % These are all valid usages. Note that here the first and second arguments | |
74 % are required and the types of the second and fourth arguments are | |
75 % checked. | |
76 % | |
77 % outerFunction(obj,... | |
78 % '-first' , 1 ,... | |
79 % '-second' , MvnDist() ,... | |
80 % '-third' , 22 ,... | |
81 % '-fourth' , 10 ); | |
82 % | |
83 % outerFunction(obj,'-fourth',3,'-second',MvnDist(),'-first',12); | |
84 % outerFunction(obj,1,MvnDist(),3); | |
85 % outerFunction(obj,1,MvnDist(),3,[]); | |
86 % outerFunction(obj,'-first',1,'-second',DiscreteDist(),'-third',[]); | |
87 % outerFunction(obj,1,MvnDist(),'-fourth',10); | |
88 % | |
89 % | |
90 % function [a,b,c,d] = outerFunction(obj,varargin) | |
91 % [a,b,c,d] = processArgs(varargin ,... | |
92 % '*-first' , [] ,... | |
93 % '*+-second' , MvnDist() ,... | |
94 % '-third' , 18 ,... | |
95 % '+-fourth' , 23 ); | |
96 % end | |
97 %% CONSTANTS | |
98 PREFIX = '-'; % prefix that must precede the names of arguments. | |
99 REQ = '*'; % require the argument | |
100 TYPE = '+'; % check the type of the arg against the default type | |
101 | |
102 % set to true for more exhaustive error checking or false for faster | |
103 % execution. | |
104 FULL_ERROR_CHECK = true; | |
105 | |
106 %% PROCESS VARARGIN - PASSED BY PROGRAMMER | |
107 | |
108 %% Check Initial Inputs | |
109 if ~iscell(userArgs) ,throwAsCaller(MException('PROCESSARGS:noUserArgs','PROGRAMMER ERROR - you must pass in the user''s arguments in a cell array as in processArgs(varargin,''-name'',val,...)'));end | |
110 if isempty(varargin) ,throwAsCaller(MException('PROCESSARGS:emptyVarargin','PROGRAMMER ERROR - you have not passed in any name/default pairs to processArgs')); end | |
111 %% Extract Programmer Argument Names and Markers | |
112 progArgNames = varargin(1:2:end); | |
113 maxNargs = numel(progArgNames); | |
114 required = cellfun(@(c)any(REQ==c(1:min(3,end))),progArgNames); | |
115 typecheck = cellfun(@(c)any(TYPE==c(1:min(3,end))),progArgNames); | |
116 if ~iscellstr(progArgNames) ,throwAsCaller(MException('PROCESSARGS:notCellStr ',sprintf('PROGRAMMER ERROR - you must pass to processArgs name/default pairs'))); end | |
117 %% Remove * and + Markers | |
118 try | |
119 progArgNames(required | typecheck) = ... | |
120 cellfuncell(@(c)c(c~=REQ & c~=TYPE) ,... | |
121 progArgNames(required | typecheck)); | |
122 catch ME | |
123 if strcmp(ME.identifier,'MATLAB:UndefinedFunction') | |
124 err = MException('PROCESSARGS:missingExternalFunctions','ProcessArgs requires the following external functions available in PMTK2: catString, cellfuncell, interweave, isprefix. Please add these to your MATLAB path.'); | |
125 throw(addCause(err,ME)); | |
126 else | |
127 rethrow(ME); | |
128 end | |
129 end | |
130 %% Set Default Values | |
131 defaults = varargin(2:2:end); | |
132 varargout = defaults; | |
133 %% Check Programmer Supplied Arguments | |
134 if mod(numel(varargin),2) ,throwAsCaller(MException('PROCESSARGS:oddNumArgs',sprintf('PROGRAMMER ERROR - you have passed in an odd number of arguments to processArgs, which requires name/default pairs'))); end | |
135 if any(cellfun(@isempty,progArgNames)) ,throwAsCaller(MException('PROCESSARGS:emptyStrName ',sprintf('PROGRAMMER ERROR - empty-string names are not allowed')));end | |
136 if nargout ~= 1 && nargout ~= maxNargs ,throwAsCaller(MException('PROCESSARGS:wrongNumOutputs',sprintf('PROGRAMMER ERROR - processArgs requires the same number of output arguments as named/default input pairs'))); end | |
137 if ~isempty(PREFIX) && ... | |
138 ~all(cellfun(@(c)~isempty(c) &&... | |
139 c(1)==PREFIX,progArgNames)) | |
140 throwAsCaller(MException('PROCESSARGS:missingPrefix',sprintf('PROGRAMMER ERROR - processArgs requires that each argument name begin with the prefix %s',PREFIX))); | |
141 end | |
142 if FULL_ERROR_CHECK && ... | |
143 numel(unique(progArgNames)) ~= numel(progArgNames) ,throwAsCaller(MException('PROCESSARGS:duplicateName',sprintf('PROGRAMMER ERROR - you can not use the same argument name twice')));end | |
144 %% PROCESS USERARGS | |
145 | |
146 %% Error Check User Args | |
147 if numel(userArgs) == 0 && nargout > 1 | |
148 if any(required) ,throwAsCaller(MException('PROCESSARGS:missingReqArgs',sprintf('The following required arguments were not specified:\n%s',catString(progArgNames(required))))); | |
149 else return; | |
150 end | |
151 end | |
152 if FULL_ERROR_CHECK | |
153 % slow, but helpful in transition from process_options to processArgs | |
154 % checks for missing '-' | |
155 if ~isempty(PREFIX) | |
156 userstrings = lower(... | |
157 userArgs(cellfun(@(c)ischar(c) && size(c,1)==1,userArgs))); | |
158 problem = ismember(... | |
159 userstrings,cellfuncell(@(c)c(2:end),progArgNames)); | |
160 if any(problem) | |
161 if sum(problem) == 1, warning('processArgs:missingPrefix','The specified value ''%s'', matches an argument name, except for a missing prefix %s. It will be interpreted as a value, not a name.',userstrings{problem},PREFIX) | |
162 else warning('processArgs:missingPrefix','The following values match an argument name, except for missing prefixes %s:\n\n%s\n\nThey will be interpreted as values, not names.',PREFIX,catString(userstrings(problem))); | |
163 end | |
164 end | |
165 end | |
166 end | |
167 %% Find User Arg Names | |
168 userArgNamesNDX = find(cellfun(@(c)ischar(c) &&... | |
169 ~isempty(c) &&... | |
170 c(1)==PREFIX,userArgs)); | |
171 %% Check User Arg Names | |
172 if ~isempty(userArgNamesNDX) && ... | |
173 ~isequal(userArgNamesNDX,userArgNamesNDX(1):2:numel(userArgs)-1) | |
174 if isempty(PREFIX), throwAsCaller(MException('PROCESSARGS:missingVal',sprintf('\n(1) every named argument must be followed by its value\n(2) no positional argument may be used after the first named argument\n'))); | |
175 else throwAsCaller(MException('PROCESSARGS:posArgAfterNamedArg',sprintf('\n(1) every named argument must be followed by its value\n(2) no positional argument may be used after the first named argument\n(3) every argument name must begin with the ''%s'' character\n(4) values cannot be strings beginning with the %s character\n',PREFIX,PREFIX))); | |
176 end | |
177 end | |
178 if FULL_ERROR_CHECK && ... | |
179 ~isempty(userArgNamesNDX) && ... | |
180 numel(unique(userArgs(userArgNamesNDX))) ~= numel(userArgNamesNDX) | |
181 throwAsCaller(MException('PROCESSARGS:duplicateUserArg',sprintf('You have specified the same argument name twice'))); | |
182 end | |
183 %% Extract Positional Args | |
184 argsProvided = false(1,maxNargs); | |
185 if isempty(userArgNamesNDX) | |
186 positionalArgs = userArgs; | |
187 elseif userArgNamesNDX(1) == 1 | |
188 positionalArgs = {}; | |
189 else | |
190 positionalArgs = userArgs(1:userArgNamesNDX(1)-1); | |
191 end | |
192 %% Check For Too Many Inputs | |
193 if numel(positionalArgs) + numel(userArgNamesNDX) > maxNargs ,throwAsCaller(MException('PROCESSARGS:tooManyInputs',sprintf('You have specified %d too many arguments to the function',numel(positionalArgs)+numel(userArgNamesNDX)- maxNargs)));end | |
194 %% Process Positional Args | |
195 for i=1:numel(positionalArgs) | |
196 % don't overwrite default value if positional arg is | |
197 % empty, i.e. '',{},[] | |
198 if ~isempty(userArgs{i}) | |
199 argsProvided(i) = true; | |
200 if typecheck(i) && ~isa(userArgs{i},class(defaults{i})) ,throwAsCaller(MException('PROCESSARGS:argWrongType',sprintf('Argument %d must be of type %s',i,class(defaults{i})))); end | |
201 varargout{i} = userArgs{i}; | |
202 end | |
203 end | |
204 %% Process Named Args | |
205 userArgNames = userArgs(userArgNamesNDX); | |
206 userProgMap = zeros(1,numel(userArgNames)); | |
207 usedProgArgNames = false(1,numel(progArgNames)); | |
208 for i=1:numel(userArgNames) | |
209 for j=1:numel(progArgNames) | |
210 if ~usedProgArgNames(j) && strcmpi(userArgNames{i},progArgNames{j}) | |
211 userProgMap(i) = j; | |
212 usedProgArgNames(j) = true; | |
213 break; | |
214 end | |
215 end | |
216 end | |
217 %% Error Check User Args | |
218 if any(~userProgMap) ,throwAsCaller(MException('PROCESSARGS:invalidArgNames',sprintf('The following argument names are invalid: %s',catString(userArgNames(userProgMap == 0),' , ')))); end | |
219 if any(userProgMap <= numel(positionalArgs)) ,throwAsCaller(MException('PROCESSARGS:bothPosAndName' ,sprintf('You cannot specify an argument positionally, and by name in the same function call.')));end | |
220 %% Extract User Values | |
221 userValues = userArgs(userArgNamesNDX + 1); | |
222 %% Type Check User Args | |
223 if any(typecheck) | |
224 for i=1:numel(userArgNamesNDX) | |
225 if typecheck(userProgMap(i)) && ... | |
226 ~isa(userArgs{userArgNamesNDX(i)+1},... | |
227 class(defaults{userProgMap(i)})) | |
228 throwAsCaller(MException('PROCESSARGS:wrongType',sprintf('Argument %s must be of type %s',userArgs{userArgNamesNDX(i)},class(defaults{userProgMap(i)})))); | |
229 end | |
230 end | |
231 end | |
232 varargout(userProgMap) = userValues; | |
233 %% Check Required Args | |
234 argsProvided(userProgMap) = true; | |
235 if any(~argsProvided & required) ,throwAsCaller(MException('PROCESSARGS:emptyVals',sprintf('The following required arguments were either not specified, or were given empty values:\n%s',catString(progArgNames(~argsProvided & required))))); end | |
236 %% Relay Mode | |
237 if nargout == 1 && (numel(varargin) > 2 || (numel(varargin) == 1 && isprefix('-',varargin{1}))) | |
238 varargout = {interweave(progArgNames,varargout)}; | |
239 end | |
240 | |
241 end | |
242 | |
243 | |
244 function s = catString(c,delim) | |
245 % Converts a cell array of strings to a single string, (i.e. single-row | |
246 % character array). The specified delimiter, delim, is added between each | |
247 % entry. Include any spaces you want in delim. If delim is not specified, | |
248 % ', ' is used instead. If c is already a string, it is just returned. If c | |
249 % is empty, s = ''. | |
250 % | |
251 % EXAMPLE: | |
252 % | |
253 % s = catString({'touch /tmp/foo';'touch /tmp foo2';'mkdir /tmp/test'},' && ') | |
254 % s = | |
255 % touch /tmp/foo && touch /tmp foo2 && mkdir /tmp/test | |
256 | |
257 if nargin == 0; s = ''; return; end | |
258 if ischar(c), s=c; | |
259 if strcmp(s,','),s = '';end | |
260 return; | |
261 end | |
262 if isempty(c),s=''; return;end | |
263 if nargin < 2, delim = ', '; end | |
264 s = ''; | |
265 for i=1:numel(c) | |
266 s = [s, rowvec(c{i}),rowvec(delim)]; %#ok | |
267 end | |
268 s(end-numel(delim)+1:end) = []; | |
269 if strcmp(s,','),s = '';end | |
270 end | |
271 | |
272 | |
273 function out = cellfuncell(fun, C, varargin) | |
274 out = cellfun(fun, C, varargin{:},'UniformOutput',false); | |
275 end | |
276 | |
277 function C = interweave(A,B) | |
278 % If A, B are two cell arrays of length N1, N2, C is a cell array of length | |
279 % N1 + N2 where where C(1) = A(1), C(2) = B(1), C(3) = A(2), C(4) = B(2), ... etc | |
280 % Note, C is always a row vector. If one cell array is longer than the | |
281 % other the remaining elements of the longer cell array are added to the | |
282 % end of C. A and B are first converted to column vectors. | |
283 A = A(:); B = B(:); | |
284 C = cell(length(A)+length(B),1); | |
285 counter = 1; | |
286 while true | |
287 if ~isempty(A) | |
288 C(counter) = A(1); A(1) = []; | |
289 counter = counter + 1; | |
290 end | |
291 if ~isempty(B) | |
292 C(counter) = B(1); B(1) = []; | |
293 counter = counter + 1; | |
294 end | |
295 if isempty(A) && isempty(B) | |
296 break; | |
297 end | |
298 end | |
299 C = C'; | |
300 end | |
301 | |
302 function p = isprefix(short,long) | |
303 % ISPREFIX Tests if the first arg is a prefix of the second. | |
304 % The second arg may also be a cell array of strings, in which case, each | |
305 % is tested. CASE SENSITIVE! | |
306 % | |
307 % If the second argument is not a string, p = false, it does not error. | |
308 % | |
309 % EXAMPLES: | |
310 % | |
311 % isprefix('foo','foobar') | |
312 % ans = | |
313 % 1 | |
314 % | |
315 %isprefix('test_',{'test_MvnDist','test_DiscreteDist','UnitTest'}) | |
316 %ans = | |
317 % 1 1 0 | |
318 error(nargchk(2,2,nargin)); | |
319 if ischar(long) | |
320 p = strncmp(long,short,length(short)); | |
321 elseif iscell(long) | |
322 p = cellfun(@(c)isprefix(short,c),long); | |
323 else | |
324 p = false; | |
325 end | |
326 end | |
327 | |
328 | |
329 function x = rowvec(x) | |
330 x = x(:)'; | |
331 end | |
332 | |
333 function x = colvec(x) | |
334 x = x(:); | |
335 end | |
336 | |
337 | |
338 | |
339 | |
340 | |
341 | |
342 | |
343 | |
344 | |
345 | |
346 |