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