1 ## Copyright (C) 2011-2012 Carnë Draug <carandraug+dev@gmail.com>
3 ## This program is free software; you can redistribute it and/or modify it under
4 ## the terms of the GNU General Public License as published by the Free Software
5 ## Foundation; either version 3 of the License, or (at your option) any later
8 ## This program is distributed in the hope that it will be useful, but WITHOUT
9 ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 ## You should have received a copy of the GNU General Public License along with
14 ## this program; if not, see <http://www.gnu.org/licenses/>.
16 function inPar = subsref (inPar, idx)
18 if ( !isa (inPar, 'inputParser') )
19 error ("object must be of the inputParser class but '%s' was used", class (inPar) );
20 elseif ( idx(1).type != '.' )
21 error ("invalid index for class %s", class (inPar) );
24 ## the following at the end may allow to use the obj.method notation one day
25 ## jwe is very against this ugly hack
26 ## what would happen if the user has the obj inside a struct? Bad things!
28 # assignin('caller', ori, inPar);
34 inPar = retrieve_results (inPar, idx)
36 inPar = inPar.Parameters;
38 inPar = parse_args (inPar, idx);
41 case {'addOptional', 'addParamValue', 'addRequired', 'addSwitch'}
42 inPar = check_methods (inPar, idx);
44 error ("invalid index for reference of class %s", class (inPar) );
47 ## TODO we should make inPar an object of the inputParser class again. At
48 ## least after running parse it becomes just a structure again. While that is
49 ## bad, at least allows for easy access to the Results and Unmatched fields
50 ## without extra coding.
51 # inPar = class (inPar, 'inputParser');
55 function out = retrieve_results (inPar, idx)
57 if ( numel(idx) != 2 || idx(2).type != '.' )
58 print_usage ("@inputParser/Results");
61 out = inPar.Results.(idx(2).subs);
66 ## when parsing options, here's the principle: Required options have to be the
67 ## first ones. They are followed by Optional if any. In the end come the
68 ## ParamValue mixed with Switch. Any other order makes no sense
69 function inPar = parse_args (inPar, idx)
71 ## syntax is inPar.parse (arguments)
72 if ( numel(idx) != 2 || idx(2).type != '()' )
73 print_usage ("@inputParser/parse");
76 ## this makes it easier to read but may be memory instensive
79 ## make copy of ordered list of Parameters to keep the original intact and readable
80 inPar.copy = inPar.Parameters;
82 if ( numel (fieldnames (inPar.Required)) > numel (args) )
83 error("%sNot enough arguments", inPar.FunctionName);
86 ## we take names out of 'copy' and values out of 'args', evaluate them and
87 ## store them into 'Results'
88 for i = 1 : numel (fieldnames (inPar.Required))
89 [name, inPar.copy] = shift (inPar.copy);
90 [value, args] = shift (args);
91 if ( !feval (inPar.Required.(name).validator, value) )
92 error_invalid (inPar.FunctionName, name, inPar.Required.(name).validator);
94 inPar.Results.(name) = value;
97 ## loop a maximum #times of the number of Optional, similarly to the required
98 ## loop. Once ran out of 'args', move their name into usingDefaults, place
99 ## their default values into 'Results', and break
101 ## because if an argument is string and does not validate, should be considered
103 found_possible_key = false;
105 for i = 1 : numel (fieldnames (inPar.Optional))
106 if ( !numel (args) || found_possible_key)
107 ## loops the number of Optional options minus the number of them already processed
108 for n = 1 : (numel (fieldnames (inPar.Optional)) - i + 1 )
109 [name, inPar.copy] = shift (inPar.copy);
110 inPar.UsingDefaults = push (inPar.UsingDefaults, name);
111 inPar.Results.(name) = inPar.Optional.(name).default;
115 [name, inPar.copy] = shift (inPar.copy);
116 [value, args] = shift (args);
117 if ( !feval (inPar.Optional.(name).validator, value) )
119 ## maybe the other optional are not defined, this can be Paramvalue
120 ## place this one on defaults and go back to the top with note to clean loop
121 inPar.UsingDefaults = push (inPar.UsingDefaults, name);
122 inPar.Results.(name) = inPar.Optional.(name).default;
123 found_possible_key = true;
124 args = unshift (args, value);
127 error_invalid (inPar.FunctionName, name, inPar.Optional.(name).validator);
130 inPar.Results.(name) = value;
134 ## loop a maximum #times of the number of ParamValue, taking pairs of keys and
135 ## values out 'args'. We no longer expect an order so we need the index in
136 ## 'copy' to remove it from there. Once ran out of 'args', move their name
137 ## into usingDefaults, place their default values into 'Results', and break
138 for i = 1 : (numel (fieldnames (inPar.ParamValue)) + numel (fieldnames (inPar.Switch)))
140 ## loops the number of times left in 'copy' since these are the last type
141 for n = 1 : numel (inPar.copy)
142 [name, inPar.copy] = shift (inPar.copy);
143 inPar.UsingDefaults = push (inPar.UsingDefaults, name);
144 if (isfield (inPar.ParamValue, name))
145 inPar.Results.(name) = inPar.ParamValue.(name).default;
147 inPar.Results.(name) = inPar.Switch.(name).default;
152 [key, args] = shift (args);
154 error("%sParameter/Switch names must be strings", inPar.FunctionName);
156 if (inPar.CaseSensitive)
157 index = find( strcmp(inPar.copy, key));
159 index = find( strcmpi(inPar.copy, key));
161 ## we can't use isfield here to support case insensitive
162 if (any (strcmpi (fieldnames (inPar.Switch), key)))
166 ## then it must be a ParamValue (even if unmatched), shift its value
167 if (numel (args) < 1)
168 error ("%sparameter '%s' does not have a value", inPar.FunctionName, key);
170 [value, args] = shift (args);
171 method = "ParamValue";
174 ## empty index means no match so either return error or move them into 'Unmatched'
175 if (!isempty (index))
176 [name, inPar.copy] = shift (inPar.copy, index);
177 if ( !feval (inPar.(method).(name).validator, value))
178 error_invalid (inPar.FunctionName, key, inPar.(method).(name).validator);
180 ## we use the name shifted from 'copy' instead of the key from 'args' in case
181 ## the key is in the wrong case
182 inPar.Results.(name) = value;
183 elseif (isempty (index) && inPar.KeepUnmatched )
184 inPar.Unmatched.(key) = value;
185 i = i - 1; # this time didn't count, go back one
187 error ("%sargument '%s' did not match any valid parameter of the parser", inPar.FunctionName, key);
191 ## if there's leftovers they must be unmatched. Note that some unmatched can
192 ## have already been processed in the ParamValue loop
193 if ( numel (args) && inPar.KeepUnmatched )
194 for i = 1 : ( numel(args) / 2 )
195 [key, args] = shift (args);
196 [value, args] = shift (args);
197 inPar.Unmatched.(key) = value;
199 elseif ( numel (args) )
200 error("%sfound unmatched parameters at end of arguments list", inPar.FunctionName);
203 ## remove copied field, keep it clean
204 inPar = rmfield (inPar, 'copy');
209 function inPar = check_methods (inPar, idx)
211 ## this makes it easier to read but is more memory intensive?
212 method = idx(1).subs;
214 func = sprintf ("@inputParser/%s", method);
216 if ( idx(2).type != '()' )
220 [name, args] = shift (args);
221 ## a validator is optional but that complicates handling all the parsing with
222 ## few functions and conditions. If not specified @() true will always return
223 ## true. Simply using true is not enough because if the argument is zero it
224 ## return false and if it's too large, takes up memory
226 case {'addOptional', 'addParamValue'}
227 if ( numel (args) == 1 )
229 elseif ( numel (args) == 2 )
230 args{2} = validate_validator (args{2});
234 [def, val] = args{:};
236 if ( numel (args) == 0 )
238 elseif ( numel (args) == 1 )
239 val = validate_validator (args{1});
245 if ( numel (args) == 0 )
252 error ("invalid index for reference of class %s", class (inPar) );
255 inPar = validate_args (method(4:end), inPar, name, val, def);
259 ## because we are nice we also support using the name of a function and not only
261 function val = validate_validator (val)
263 val = str2func (val);
264 elseif ( !isa (val, 'function_handle') )
265 error ("validator must be a function handle or the name of a valid function");
269 ## to have a single function that handles them all, something must be done with
270 ## def value, because addRequire does not have those. That's why the order of
271 ## the last two args is different from the rest and why 'def' has a default value
272 function inPar = validate_args (method, inPar, name, val, def = false)
274 if ( !strcmp (class (inPar), 'inputParser') )
275 error ("object must be of the inputParser class but '%s' was used", class (inPar) );
276 elseif ( !isvarname (name) )
277 error ("invalid variable name in argname");
280 ## because the order arguments are specified are the order they are expected,
281 ## can't have ParamValue/Switch before Optional, and Optional before Required
282 n_optional = numel (fieldnames (inPar.Optional));
283 n_params = numel (fieldnames (inPar.ParamValue));
284 n_switch = numel (fieldnames (inPar.Switch));
285 if ( strcmp (method, 'Required') && ( n_optional || n_params || n_switch) )
286 error ("Can't specify 'Required' arguments after Optional, ParamValue or Switch");
287 elseif ( strcmp (method, 'Optional') && ( n_params || n_switch) )
288 error ("Can't specify 'Optional' arguments after ParamValue or Switch");
291 ## even if CaseSensitive is turned on, we still shouldn't have two args with
292 ## the same. What if they decide to change in the middle of specifying them?
293 if ( any (strcmpi (inPar.Parameters, name)) )
294 error ("argname '%s' has already been specified", name);
296 inPar.Parameters = push (inPar.Parameters, name);
297 inPar.(method).(name).default = def;
298 inPar.(method).(name).validator = val;
301 ## make sure that the given default value is actually valid
302 ## TODO make sure that when using the default, it's only validated once
303 if ( isa (val, 'function_handle') && !strcmpi (method, 'Required') && !feval (val, def) )
304 error ("default value for '%s' failed validation with '%s'", name, func2str (val) );
309 ## this is just for consistency of error message
310 function error_invalid (prefix, name, val)
311 error("%sargument '%s' failed validation %s", prefix, name, func2str (val));
314 ################################################################################
315 ## very auxiliary functions
316 ################################################################################
318 function [out, in] = shift (in, idx = 1)
323 function [in] = unshift (in, add)
327 in (numel(add) + 1 : end + numel(add)) = in;
328 in (1:numel(add)) = add;
331 function [in] = push (in, add)
335 in( end+1 : end+numel(add) ) = add;