function options = loadOptions(defaults,args)
%LOADOPTIONS Loads option-value pairs into an options struct
%
%loadOptions can be used to create an option-value pair struct for
%functions, from function arguments. This function allows for the user to
%choose default values for unspecified options, and specify types (i.e. a
%MATLAB class) for the option values.
%
%This is useful for functions that will typically be called with only a few
%options set from a large set of possible options.
%
%This function provides for typing of the option values. If a class is
%specified for an option, values must be of that class or a subclass of
%that class, or an error results.
%
%Usage:
%
% A function should be written as follows:
% function <r> = userFunction(<arg1>,<arg2>,...,<argN>,varargin)
% {arg1,...} are all the arguments to the user function that are not
% options. Option-value pairs will be loaded into varargin. The usage
% of the user function will be as follows:
% r = userFunction(arg1,arg2,...,argN,'Option1',value1,...)
%
% Create a default options struct array, where each element has
% the following fields:
%
% defaults(i).name = <option name>
% <option name> should follow the rules for naming variables:
% - No special characters or spaces
% - The first character must be alphabetic
% defaults(i).value = <default option value>
% defaults(i).class = [] | <MATLAB class name>
% If defaults(i).class is empty, the option is untyped. Note
% that subclasses of defaults(i).class will be considered valid
% options.
%
% Now this struct should be passed along with varargin to loadOptions, as
% follows:
% options = loadOptions(defaults,varargin);
%
% The fields of the options struct will be the option names, and the values
% will be either the default values for those options not specified, or the
% given values for those options specified in the function arguments.
%
%Example:
%
% We wish to write a shift function for a bit register. The options will be:
% 'RegisterSize' - Has a default of 8, must be a uint16
% 'SignBit' - Must be a boolean, default false
% 'Display' - Something to display at the end of the function, may be
% unsigned, default ''
%
% Our function will be written:
%
% function vOut = shift(v,delta,varargin)
%
% defaults = struct('name',{'RegisterSize','SignBit','Display'},...
% 'value',{8,false,''},'class',{'uint16','boolean',''});
% options = loadOptions(defaults,varargin);
%
% if options.SignBit, start=2; else start=1; end
%
% for i=start:options.RegisterSize
% if i<delta+1, vOut(i) = 0;
% else vOut(i) = v(i-delta); end
% end
%
% if ~isempty(options.Display), disp(options.Display); end
%
% Now consider various user inputs:
%
% >> shift(ones(1,8),2)
% ans =
% 0 0 1 1 1 1 1 1
%
% >> shift(ones(1,4),2,'RegisterSize',4)
% ??? Error using ==> loadOptions at ---
% 'RegisterSize' must be of type 'uint16'
%
% >> shift(ones(1,4),2,'RegisterSize',uint16(4))
% ans =
% 0 0 1 1
%
% >> shift(ones(1,4),2,'RegisterSize',uint16(4),'Display','foo!')
% foo!
% ans =
% 0 0 1 1
%
% >> shift(ones(1,4),2,'RegisterSize',uint16(4),'Display',.01)
% 0.0100
% ans =
% 0 0 1 1
%
% >> shift(ones(1,4),2,'RegisterSize')
% ??? Error using ==> loadOptions at ---
% Options must be name-value pairs
%
% >> shift(ones(1,4),2,4,4)
% ??? Error using ==> loadOptions at ---
% Option names must be strings
%
% >> shift(ones(1,4),2,'xyzzy',4)
% ??? Error using ==> loadOptions at ---
% 'xyzzy' is not a valid option
%
% (c) 2007 Nathaniel Brahms and the Massachusetts Institute of Technology
%
% Author: N. Brahms
% Email: Contact via the Mathworks website
% Date: 11/18/2007
% Version: 1.0
% History:
% Date Version Notes
% --------------------------------------------------------------------
% 11/18/2007 1.0 First version
% args length must be even
if mod(numel(args),2)~=0
error('Options must be name-value pairs');
end
names = cell(size(defaults));
% Loop over options
for i=1:length(defaults)
names{i} = defaults(i).name;
options.(names{i}) = defaults(i).value;
end
% Loop over option pairs
for iPair=1:length(args)/2
if ~ischar(args{2*iPair-1})
error('Option names must be strings');
end
% Finds the index in defaults corresponding to this option
iOption = find(strcmp(names,args{2*iPair-1}),1);
% The option was not found
if isempty(iOption)
error('''%s'' is not a valid option',args{2*iPair-1});
end
% If the option was found, extract the value and test its class if
% defaults(iOption).class is not empty
test = args{2*iPair};
if ~isempty(defaults(iOption).class) && ~isa(test,defaults(iOption).class)
error('''%s'' must be of type ''%s''',defaults(iOption).name,...
defaults(iOption).class);
end
% Output the option value
options.(defaults(iOption).name) = test;
end % for iPair