]> Creatis software - CreaPhase.git/blob - octave_packages/general-1.3.1/@inputParser/subsref.m
Add a useful package (from Source forge) for octave
[CreaPhase.git] / octave_packages / general-1.3.1 / @inputParser / subsref.m
1 ## Copyright (C) 2011-2012 CarnĂ« Draug <carandraug+dev@gmail.com>
2 ##
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
6 ## version.
7 ##
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
11 ## details.
12 ##
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/>.
15
16 function inPar = subsref (inPar, idx)
17
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) );
22   endif
23
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!
27 #  ori = inputname(1);
28 #  assignin('caller', ori, inPar);
29
30   method = idx(1).subs;
31
32   switch method
33   case 'Results'
34     inPar = retrieve_results (inPar, idx)
35   case 'Parameters'
36     inPar = inPar.Parameters;
37   case 'parse'
38     inPar = parse_args (inPar, idx);
39   case 'Unmatched'
40   case 'UsingDefaults'
41   case {'addOptional', 'addParamValue', 'addRequired', 'addSwitch'}
42     inPar = check_methods (inPar, idx);
43   otherwise
44     error ("invalid index for reference of class %s", class (inPar) );
45   endswitch
46
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');
52
53 endfunction
54
55 function out = retrieve_results (inPar, idx)
56
57   if ( numel(idx) != 2 || idx(2).type != '.' )
58     print_usage ("@inputParser/Results");
59   endif
60
61   out = inPar.Results.(idx(2).subs);
62
63 endfunction
64
65
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)
70
71   ## syntax is inPar.parse (arguments)
72   if ( numel(idx) != 2 || idx(2).type != '()' )
73     print_usage ("@inputParser/parse");
74   endif
75
76   ## this makes it easier to read but may be memory instensive
77   args = idx(2).subs;
78
79   ## make copy of ordered list of Parameters to keep the original intact and readable
80   inPar.copy = inPar.Parameters;
81
82   if ( numel (fieldnames (inPar.Required)) > numel (args) )
83     error("%sNot enough arguments", inPar.FunctionName);
84   endif
85
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);
93     endif
94     inPar.Results.(name) = value;
95   endfor
96
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
100
101   ## because if an argument is string and does not validate, should be considered
102   ## a ParamValue key
103   found_possible_key = false;
104
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;
112       endfor
113       break
114     endif
115     [name, inPar.copy] = shift (inPar.copy);
116     [value, args]      = shift (args);
117     if ( !feval (inPar.Optional.(name).validator, value) )
118       if (ischar (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);
125         continue
126       else
127         error_invalid (inPar.FunctionName, name, inPar.Optional.(name).validator);
128       endif
129     else
130       inPar.Results.(name) = value;
131     endif
132   endfor
133
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)))
139     if ( !numel (args) )
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;
146         else
147           inPar.Results.(name) = inPar.Switch.(name).default;
148         endif
149       endfor
150       break
151     endif
152     [key, args] = shift (args);
153     if ( !ischar (key) )
154       error("%sParameter/Switch names must be strings", inPar.FunctionName);
155     endif
156     if (inPar.CaseSensitive)
157       index = find( strcmp(inPar.copy, key));
158     else
159       index = find( strcmpi(inPar.copy, key));
160     endif
161     ## we can't use isfield here to support case insensitive
162     if (any (strcmpi (fieldnames (inPar.Switch), key)))
163       value  = true;
164       method = "Switch";
165     else
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);
169       endif
170       [value, args] = shift (args);
171       method = "ParamValue";
172     endif
173
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);
179       endif
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
186     else
187       error ("%sargument '%s' did not match any valid parameter of the parser", inPar.FunctionName, key);
188     endif
189   endfor
190
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;
198     endfor
199   elseif ( numel (args) )
200     error("%sfound unmatched parameters at end of arguments list", inPar.FunctionName);
201   endif
202
203   ## remove copied field, keep it clean
204   inPar = rmfield (inPar, 'copy');
205
206 endfunction
207
208
209 function inPar = check_methods (inPar, idx)
210
211   ## this makes it easier to read but is more memory intensive?
212   method  = idx(1).subs;
213   args    = idx(2).subs;
214   func    = sprintf ("@inputParser/%s", method);
215
216   if ( idx(2).type != '()' )
217     print_usage (func);
218   endif
219   def_val      = @() true;
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
225   switch method
226   case {'addOptional', 'addParamValue'}
227     if     ( numel (args) == 1 )
228       args{2} = def_val;
229     elseif ( numel (args) == 2 )
230       args{2} = validate_validator (args{2});
231     else
232       print_usage(func);
233     endif
234     [def, val] = args{:};
235   case {'addRequired'}
236     if     ( numel (args) == 0 )
237       val = def_val;
238     elseif ( numel (args) == 1 )
239       val = validate_validator (args{1});
240     else
241       print_usage(func);
242     endif
243     def = false;
244   case {'addSwitch'}
245     if ( numel (args) == 0 )
246       val = def_val;
247       def = false;
248     else
249       print_usage(func);
250     endif
251   otherwise
252     error ("invalid index for reference of class %s", class (inPar) );
253   endswitch
254
255   inPar = validate_args (method(4:end), inPar, name, val, def);
256
257 endfunction
258
259 ## because we are nice we also support using the name of a function and not only
260 ## a function handle
261 function val = validate_validator (val)
262   if ( ischar (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");
266   end
267 endfunction
268
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)
273
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");
278   endif
279
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");
289   endif
290
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);
295   else
296     inPar.Parameters = push (inPar.Parameters, name);
297     inPar.(method).(name).default = def;
298     inPar.(method).(name).validator = val;
299   endif
300
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) );
305   endif
306
307 endfunction
308
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));
312 endfunction
313
314 ################################################################################
315 ## very auxiliary functions
316 ################################################################################
317
318 function [out, in] = shift (in, idx = 1)
319   out     = in{idx};
320   in(idx) = [];
321 endfunction
322
323 function [in] = unshift (in, add)
324   if ( !iscell (add) )
325     add = {add};
326   endif
327   in (numel(add) + 1 : end + numel(add)) = in;
328   in (1:numel(add)) = add;
329 endfunction
330
331 function [in] = push (in, add)
332   if ( !iscell (add) )
333     add = {add};
334   endif
335   in( end+1 : end+numel(add) ) = add;
336 endfunction