from argcheck by Peder Axensten
Sets missing input args to the defaults, does argument checking, and handles property id/value lists

argcheck(maxargs, inargs, defaults, varargin)
function varargout= argcheck(maxargs, inargs, defaults, varargin)

% USAGE:       [err, <outargs>]= argcheck(maxargs, inargs, defaults)
%              [err, <outargs>]= argcheck(maxargs, inargs, defaults, <check list>)
% 
% Sets missing input args to the defaults, does argument checking, and handles property id/value lists. 
%  Although not slow, the emphasis is on error checking, not on speed. 
%  If a value is tagged as 'proplist' the property id/value list will be returned as a structure. 
%  Further, if the default value for it is a structure, only property ids that are field names of the 
%     structure are allowed (the structure's field names must be lower case!). If you don't want this 
%     check, make the default an empty value, e.g. {}.
% 
% EXAMPLES:
% Say you have three arguments x, a, and b, the two latter optional with default values a=0 and b=1. 
% Using the function declaration 'function whateverout= funcname(varargin)' you would call
% [err, x, a, b]=  argcheck(3, varargin, {0, 1});
% You want to check if you can do an elemetwise calculation with x, a, b, e.g. x.*a+b:
% [err, x, a, b]=  argcheck(3, varargin, {0, 1}, 'mathsize');
% And b must be positive:
% [err, x, a, b]=  argcheck(3, varargin, {0, 1}, 'mathsize', 'positive', [3]);
% Maybe we want an optional property id/value list:
% [err, x, a, b, p]=  argcheck(4, varargin, {0, 1, {}}, ...
%                                           'mathsize', [1 2 3], 'positive', [3], 'proplist', [4]);
% If the returned err is not empty, signal an error with it: 
% if(~isempty(err)), error(err); end
% 
% ARGUMENTS:
% err:              Error message. If empty, all went well.
% <outargs>:        A list of receiving variables, there must be exactly maxargs of them. 
% maxargs:          Maximum number of arguments, a positive integer scalar.
% inargs:           The original input arguments, a cell vector.
% defaults:         Default values, a cell vector, where length(defaults) <= length(inargs) <= maxargs. 
% <check list>:     An optional list that specifies what tests the arguments should pass. Each 
%                   test specification consists of the test id (a string, see below) and an optional 
%                   argument index specifying the the test arguments (an array with inargs indeces).
% 
% TEST IDS:
% Check arguments' dimensions:
% - 'length'        Arguments are of the same length, uses length(arg).
% - 'mathlength'    Arguments are non-empty, numeric, and of the same length or scalar. 
% - 'length=N'      Arguments are of length N, uses length(arg) == N.
% - 'mathlength=N'  Arguments are non-empty, numeric, and of length N or scalar.
% - 'size'          Arguments are of the same size. 
% - 'mathsize'      Arguments are non-empty, numeric, and of the same size or scalar. 
% - 'size1=N'       Arguments are have N rows, uses size(arg, 1) == N.
% - 'size2=N'       Arguments are have N columns, uses size(arg, 2) == N.
% - 'non-empty'     Arguments are non-empty, uses ~isempty(arg).
% - 'scalar'        Arguments are scalars, uses isscalar.
% - 'vector'        Arguments are vectors, uses isvector. 
% - 'colvector'     Arguments are column vectors. 
% - 'rowvector'     Arguments are row vectors. 
% - 'square'        Arguments are non-empty, numeric, square, and two-dimensional arrays. 
% - 'dim=N'         Arguments are of dimension N, uses ndims(arg) == N.
% Check argument values:
% - 'numeric'       Arguments are numeric arrays, uses isa(arg, 'numeric').
% - 'real'          Arguments are numeric real arrays, uses isa(arg, 'real').
% - 'finite'        Arguments are of only finite elements, uses all(isfinite(arg)).
% - 'int'           Arguments are integer values (use 'integer' for integer TYPES, see below).
% - 'positive'      Arguments are of only positiv elements.
% - 'non-positive'  Arguments are of only non-positiv elements.
% - 'negative'      Arguments are of only negative elements.
% - 'non-negative'  Arguments are of only non-negative elements.
% - 'non-zero'      Arguments are of only non-zero elements.
% - 'proplist'      Arguments are property lists -- a cell array with an even number of items where the first,
%                       third etc. items are strings (property identifiers).
% - 'directory'     Arguments are directories, uses isdir(arg).
% Check arguments' class:
% - 'cellstr'       Arguments are cell array of strings, uses iscellstr(arg).
% - 'string'        Arguments are char or cellstr.
% - 'float'         Arguments are floating-point arrays, uses isa(arg, 'float').
% - 'integer'       Arguments are of integer data types, uses isa(arg, 'integer').
% - 'sym'           Arguments are symbolic expression, uses isa(arg, 'sym').
% - '<other>'       Arguments are of a given class, uses isa(arg, 'TAG'), e.g. sym, logical, char, single, 
%                       double, cell, struct, function_handle, int8, uint8, int16, uint16, int32, 
%                       uint32, int64, uint64, or any other class.
% 
% HISTORY:
% Version 1.0, 2006-11-16.
% Version 1.1, 2006-11-23.
% - Added check for allowed property ids in property id/value lists.
% - Added structure generation from property id/value lists.
% Version 1.2, 2006-11-27.
% - Returned arguments are no longer made into NaNs if there was an error. 
% - Removed some unnecessary case sensitivity. 
% - Added check for integer values, tag 'int'.
% - Clarified examples. 
% 
% COPYRIGHT (C) Peder Axensten <peder at axensten dot se>

% KEYWORDS:     argument check, error report, type checking, property lists
% 
% ACKNOWLEDGE:  9593, 9082
% 
% REQUIREMENTS: Tested on Matlab 7.0.1 (R14SP1), not tested, but maybe works, on earlier. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	% Check the arguments. This checks the arguments passed to argumentcheck
	if(nargin < 3),						error('There must be at least 3 arguments.');					end
	if(~isscalar(maxargs)), 			error('First argument must be a positive SCALAR integer.');		end
	if(~isnumeric(maxargs)), 			error('First argument must be a positive scalar INTEGER.');		end
	if(floor(maxargs) ~= maxargs),		error('First argument must be a positive scalar INTEGER.');		end
	if(maxargs <= 0),					error('First argument must be a POSITIVE scalar integer.');		end
	if(~iscell(inargs)),							error('Second argument must be a CELL vector.');	end
	if(~isvector(inargs) && ~isempty(inargs)),		error('Second argument must be a cell VECTOR.');	end
	if(~iscell(defaults)),							error( 'Third argument must be a CELL vector.');	end
	if(~isvector(defaults) && ~isempty(defaults)),	error( 'Third argument must be a cell VECTOR.');	end
	if(length(defaults) > maxargs),		error(['Maximum maxargs (' num2str(maxargs) ...
											') default values (now is ' num2str(length(defaults)) ').']);
	elseif(nargout ~= maxargs+1),		error(['There must be maxargs+1 (' num2str(maxargs+1) ...
											') output arguments (now is ' num2str(nargout) ').']);
	end
	
	% Checks the number of inargs and uses default values if need be.
	err=	'';
	if(length(inargs) < maxargs-length(defaults))	% To few arguments.
		err=	['\n    There must be at least ' num2str(maxargs-length(defaults)) ' arguments.'];
	elseif(length(inargs) > maxargs)				% To many arguments.
		err=	['\n    There must be at most '  num2str(maxargs)                  ' arguments.'];
	elseif(length(inargs) < maxargs)				% Set defaults. 
		inargs=	{inargs{:} defaults{end-maxargs+length(inargs)+1 : end}};
	end
	defaults2=	cell(1, maxargs);
	defaults2(maxargs-length(defaults)+1:end)=	defaults(:);
	
	% Extra checks.
	if(isempty(err)),		[err, inargs]=	extrachecks(defaults2, inargs, varargin{:});			end
	
	% Ok, we are done. Convert \n to newlines. Organise output.
	if(~isempty(err)),		err= sprintf(['Error with input arguments: ' err]);						end
	varargout=	{err inargs{:}};
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Extra checks.
function [err, inargs] = extrachecks(defaults, inargs, varargin)
	err=		'';
	N=			1;
	testlist=	{varargin{:}, {}};		% In case the last index is omitted...
	while(N < length(testlist))
		tag=		testlist{N};
		ind=		testlist{N+1};
		N=			N+1;
		
		% Check the properties given. 
		if(~ischar(tag))
			error(['Expected a string, but got a ' class(tag) ' in argument ' num2str(2+N) '.']);
		elseif(isnumeric(ind)),	N=		N+1;
		else					ind=	[];
		end
		if(isempty(ind)),		ind= 1:length(inargs);
		elseif(min(ind) < 1),	error(['Non-positive index in argument ' num2str(2+N) '.']);
		elseif(max(ind) > length(inargs))
			error(['Index to large (max ' num2str(length(inargs)) ') in argument ' num2str(2+N) '.']);
		end
		
		% Some sizes/lengths are specific, find the value. 
		tagN=		tag;
		res=		regexpi(tag, '^(length|mathlength|dim|size1|size2)=([0-9]+)$', 'tokens');
		if(~isempty(res))
			tagN=		[res{1}{1} 'N'];
			tagarg=		str2double(res{1}{2});
		end
		
		% Some tags should be checked for other stuff too. 
		if(regexpi(tag, '^(math(length|lengthn|size)|square)$'))
			testlist=	{testlist{1:N-1} 'non-empty' ind 'numeric' ind testlist{N:end}};
		end
		
		% Time to do the test. 
		switch(lower(tagN))
		case 'length'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Arguments must have the same length', tag, ...
							(celldo(inargs, ind, @length) == length(inargs{ind(1)})));
		case 'mathlength'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			ind=	intersect(ind, find(~celldo(inargs, ind, @isscalar)));
			err=	errstr(ind, err, 'Arguments must have the same length (or be scalars)', tag, ...
							(celldo(inargs, ind, @length) == length(inargs{ind(1)})));
		case 'lengthn'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, ['Arguments must be of length ' num2str(tagarg)], tag, ...
							(celldo(inargs, ind, @length) == tagarg));
		case 'mathlengthn'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			ind=	intersect(ind, find(~celldo(inargs, ind, @isscalar)));
			err=	errstr(ind, err, ...
							['Argument(s) must be of length ' num2str(tagarg) ' (or be scalar)'], ...
							tag, (celldo(inargs, ind, @length) == tagarg));
		case 'size'			%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Arguments must have the same size', tag, samesize(inargs, ind));
		case 'mathsize'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			ind=	intersect(ind, find(~celldo(inargs, ind, @isscalar)));
			err=	errstr(ind, err, 'Arguments must have the same size (or be scalar)', tag, ...
							samesize(inargs, ind));
		case 'size1n'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, ['Arguments must have ' num2str(tagarg) ' rows'], tag, ...
							(cellsz(inargs, ind, 1) == tagarg));
		case 'size2n'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, ['Arguments must have ' num2str(tagarg) ' columns'], tag, ...
							(cellsz(inargs, ind, 2) == tagarg));
		case 'non-empty'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must not be empty', tag, ...
							~celldo(inargs, ind, @isempty));
		case 'scalar'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be scalar(s)', tag, ...
							celldo(inargs, ind, @isscalar));
		case 'vector'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be vector(s)', tag, ...
							celldo(inargs, ind, @isvector));
		case 'colvector'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be column vector(s)', tag, ...
							(celldo(inargs, ind, @isvector) & (cellsz(inargs, ind, 2) == 1)));
		case 'rowvector'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be row vector(s)', tag, ...
							(celldo(inargs, ind, @isvector) & (cellsz(inargs, ind, 1) == 1)));
		case 'square'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be matrices and square', tag, ...
							(cellsz(inargs, ind, 1) == cellsz(inargs, ind, 2)) & ...
							(celldo(inargs, ind, @ndims) == 2));
		case 'dimn'			%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, ['Argument(s) must be of dimension ', num2str(tagarg)], tag, ...
							(celldo(inargs, ind, @ndims) == tagarg));
%			case 'numeric' in other!	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
		case 'real'			%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'real', 			tag, inargs, @isreal);
		case 'finite'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'finite',		tag, inargs, @isfinite);
		case 'int'			%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'integer',		tag, inargs, (@(x) floor(x) == x));
		case 'positive'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'positive',		tag, inargs, (@(x) x >  0));
		case 'non-positive'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'non-positive',	tag, inargs, (@(x) x <= 0));
		case 'negative'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'negative',		tag, inargs, (@(x) x <  0));
		case 'non-negative'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'non-negative',	tag, inargs, (@(x) x >= 0));
		case 'non-zero'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	allvalues(ind, err, 'non-zero',		tag, inargs, (@(x) x ~= 0));
		case 'proplist'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			[err, inargs]=	proplists(err, tag, defaults, inargs, ind);
		case 'directory'	%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			ind2=	find(celldo(inargs, ind, @ischar));
			err=	errstr(ind, err, 'Argument(s) must be paths to directories', tag, ...
							(celldo(inargs, ind, @ischar) & celldo(inargs, ind2, @isdir)));
		case 'cellstr'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be cell array of strings', tag, ...
							celldo(inargs, ind, @iscellstr));
		case 'string'		%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			err=	errstr(ind, err, 'Argument(s) must be char or cellstr', tag, ...
							(celldo(inargs, ind, @ischar) | celldo(inargs, ind, @iscellstr)));
		otherwise			%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			% numeric, logical, char, float, single, double, cell, struct, sym, function_handle, 
			% integer, int8, uint8, int16, uint16, int32, uint32, int64, uint64
			res=	false(size(inargs));
			for n = 1:length(ind),		res(ind(n))= isa(inargs{ind(n)}, tagN);		end
			err=	errstr(ind, err, ['Argument(s) must be of class ' tag ' (see ''help isa'')'], ...
							tag, res);
		end
	end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function err = errstr(ind, err, errmsg, tag, oklist)
	res=	setdiff(ind, find(oklist));
	if(~isempty(res))
		err= [err, '\n    ' errmsg ': argument ' num2str(res) ' [' tag ']'];
	end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function res = celldo(args, ind, func)
	res=	zeros(size(args));	% Must be zeros, not NaN, false, or true...
	for n = 1:length(ind),		res(ind(n))= func(args{ind(n)});				end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function err = allvalues(ind, err, errmsg, tag, args, func)
	res=	NaN(size(args));
	for n = 1:length(ind)
		if(isnumeric(args{ind(n)})),		res(ind(n))= 	all(all(all(func(args{ind(n)}))));
		else								res(ind(n))=	false;
		end
	end
	err=	errstr(ind, err, ['Argument(s) must contain only ' errmsg ' numeric values'], tag, res);
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function res = cellsz(args, ind, dim)
	res=	NaN(size(args));
	for n = 1:length(ind),		res(ind(n))= size(args{ind(n)}, dim);			end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function res = samesize(inargs, ind)
	res=	true(size(inargs));
	if(~isempty(ind))
		for N= 1:ndims(inargs{ind(1)})
			res=	res & (size(inargs{ind(1)}, N) == cellsz(inargs, ind, N));
		end
	end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [err, args] = proplists(err, tag, defaults, args, ind)
	res=	false(size(args));
	for n = 1:length(ind)
		a=				args{ind(n)};
		a(1:2:end)=		lower(a(1:2:end));
		res(ind(n))= 	iscell(a)								& ...
						(bitand(length(a), 2) == 0)				& ...
						iscellstr(a(1:2:end));
		if(res(ind(n)))	% Check the ids and make a struct.
			def=			defaults{ind(n)};
			if(isstruct(def))
				nonos=	setdiff(a(1:2:end), fieldnames(def));
				if(~isempty(nonos))	
					%%%%%%%%% (STILL NEED TO DELIMIT THE NONOS...) %%%%%%%%%%%%%%%%%%%%
					err=	errstr(n, err, ['Undefined property id(s) ' nonos{:}], tag, false(size(args)));
				end
			elseif(~isempty(def))
				error('To check a property value list the default must be a struct.');
			end
			if(~isempty(a)),	for m = 2:2:length(a),	def.(a{m-1})= a{m};		end;		end
			args{ind(n)}=		def;
		else
			err=	errstr(ind, err, 'Argument(s) must be list of property id/value pairs', tag, res);
		end
	end
end

Contact us at files@mathworks.com