[7b4196]: inst / @inputParser / inputParser.m Maximize Restore History

Download this file

inputParser.m    225 lines (211 with data), 10.6 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
## Copyright (C) 2011-2012 CarnĂŤ Draug <carandraug+dev@gmail.com>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, see <http://www.gnu.org/licenses/>.
## -*- texinfo -*-
## @deftypefn {Function File} {@var{parser} =} inputParser ()
## Create object @var{parser} of the inputParser class.
##
## This class is designed to allow easy parsing of function arguments. This class
## supports four types of arguments:
##
## @enumerate
## @item mandatory (see @command{@@inputParser/addRequired});
## @item optional (see @command{@@inputParser/addOptional});
## @item named (see @command{@@inputParser/addParamValue});
## @item switch (see @command{@@inputParser/addSwitch}).
## @end enumerate
##
## After defining the function API with this methods, the supplied arguments can
## be parsed with the @command{@@inputParser/parse} method and the parsing results
## accessed with the @command{Results} accessor.
##
## @deftypefnx {Accessor method} parser.Parameters
## Return list of parameters name already defined.
##
## @deftypefnx {Accessor method} parser.Results
## Return structure with argument names as fieldnames and corresponding values.
##
## @deftypefnx {Accessor method} parser.Unmatched
## Return structure similar to @command{Results} for unmatched parameters. See
## the @command{KeepUnmatched} property.
##
## @deftypefnx {Accessor method} parser.UsingDefaults
## Return cell array with the names of arguments that are using default values.
##
## @deftypefnx {Class property} parser.CaseSensitive = @var{boolean}
## Set whether matching of argument names should be case sensitive. Defaults to false.
##
## @deftypefnx {Class property} parser.FunctionName = @var{name}
## Set function name to be used on error messages. Defauls to empty string.
##
## @deftypefnx {Class property} parser.KeepUnmatched = @var{boolean}
## Set whether an error should be given for non-defined arguments. Defaults to
## false. If set to true, the extra arguments can be accessed through
## @code{Unmatched} after the @code{parse} method. Note that since @command{Switch}
## and @command{ParamValue} arguments can be mixed, it is not possible to know
## the unmatched type. If argument is found unmatched it is assumed to be of the
## @command{ParamValue} type and it is expected to be followed by a value.
##
## @deftypefnx {Class property} parser.StructExpand = @var{boolean}
## Set whether a structure can be passed to the function instead of parameter
## value pairs. Defaults to true. Not implemented yet.
##
## The following example shows how to use this class:
##
## @example
## @group
## function check (pack, path, mat, varargin)
## p = inputParser; # create object
## p.FunctionName = "check"; # set function name
## p = p.addRequired ("pack", @@ischar); # create mandatory argument
##
## p = p.addOptional ("path", pwd(), @@ischar); # create optional argument
##
## ## one can create a function handle to anonymous functions for validators
## val_mat = @@(x)isvector(x) && all( x <= 1) && all(x >= 0);
## p = p.addOptional ("mat", [0 0], val_mat);
##
## ## create two ParamValue type of arguments
## val_type = @@(x) ischar(x) && any(strcmp(x, @{"linear", "quadratic"@});
## p = p.addParamValue ("type", "linear", @@val_type);
## val_verb = @@(x) ischar(x) && any(strcmp(x, @{"low", "medium", "high"@});
## p = p.addParamValue ("tolerance", "low", @@val_verb);
##
## ## create a switch type of argument
## p = p.addSwitch ("verbose");
##
## p = p.parse (pack, path, mat, varargin@{:@});
##
## ## the rest of the function can access the input by accessing p.Results
## ## for example, to access the value of tolerance, use p.Results.tolerance
## endfunction
##
## check ("mech"); # valid, will use defaults for other arguments
## check (); # error since at least one argument is mandatory
## check (1); # error since !ischar
## check ("mech", "~/dev"); # valid, will use defaults for other arguments
##
## check ("mech", "~/dev", [0 1 0 0], "type", "linear"); # valid
##
## ## the following is also valid. Note how the Switch type of argument can be
## ## mixed into or before the ParamValue (but still after Optional)
## check ("mech", "~/dev", [0 1 0 0], "verbose", "tolerance", "high");
##
## ## the following returns an error since not all optional arguments, `path' and
## ## `mat', were given before the named argument `type'.
## check ("mech", "~/dev", "type", "linear");
## @end group
## @end example
##
## @emph{Note 1}: a function can have any mixture of the four API types but they
## must appear in a specific order. @command{Required} arguments must be the very
## first which can be followed by @command{Optional} arguments. Only the
## @command{ParamValue} and @command{Switch} arguments can be mixed together but
## must be at the end.
##
## @emph{Note 2}: if both @command{Optional} and @command{ParamValue} arguments
## are mixed in a function API, once a string Optional argument fails to validate
## against, it will be considered the end of @command{Optional} arguments and the
## first key for a @command{ParamValue} and @command{Switch} arguments.
##
## @seealso{@@inputParser/addOptional, @@inputParser/addSwitch,
## @@inputParser/addParamValue, @@inputParser/addRequired,
## @@inputParser/createCopy, @@inputParser/parse}
## @end deftypefn
function inPar = inputParser
if (nargin != 0)
print_usage;
endif
inPar = struct;
## these are not to be accessed by users. Each will have a field whose names
## are the argnames which will also be a struct with fieldnames 'validator'
## and 'default'
inPar.ParamValue = struct;
inPar.Optional = struct;
inPar.Required = struct;
inPar.Switch = struct;
## this will be filled when the methodd parse is used and will be a struct whose
## fieldnames are the argnames that return their value
inPar.Results = struct;
## an 1xN cell array with argnames. It is read only by the user and its order
## showws the order that they were added to the object (which is the order they
## will be expected)
inPar.Parameters = {};
inPar.CaseSensitive = false;
inPar.FunctionName = ''; # name of the function for the error message
inPar.KeepUnmatched = false;
inPar.StructExpand = true;
inPar.Unmatched = struct;
inPar.UsingDefaults = {};
inPar = class (inPar, 'inputParser');
endfunction
%!shared p, out
%! p = inputParser;
%! p = p.addRequired ("req1", @(x) ischar (x));
%! p = p.addOptional ("op1", "val", @(x) ischar (x) && any (strcmp (x, {"val", "foo"})));
%! p = p.addOptional ("op2", 78, @(x) (x) > 50);
%! p = p.addSwitch ("verbose");
%! p = p.addParamValue ("line", "tree", @(x) ischar (x) && any (strcmp (x, {"tree", "circle"})));
%! ## check normal use, only required are given
%! out = p.parse ("file");
%!assert ({out.Results.req1, out.Results.op1, out.Results.op2, out.Results.verbose, out.Results.line},
%! {"file" , "val" , 78 , false , "tree"});
%!assert (out.UsingDefaults, {"op1", "op2", "verbose", "line"});
%! ## check normal use, but give values different than defaults
%! out = p.parse ("file", "foo", 80, "line", "circle", "verbose");
%!assert ({out.Results.req1, out.Results.op1, out.Results.op2, out.Results.verbose, out.Results.line},
%! {"file" , "foo" , 80 , true , "circle"});
%! ## check optional is skipped and considered ParamValue if unvalidated string
%! out = p.parse ("file", "line", "circle");
%!assert ({out.Results.req1, out.Results.op1, out.Results.op2, out.Results.verbose, out.Results.line},
%! {"file" , "val" , 78 , false , "circle"});
%! ## check case insensitivity
%! out = p.parse ("file", "foo", 80, "LiNE", "circle", "vERbOSe");
%!assert ({out.Results.req1, out.Results.op1, out.Results.op2, out.Results.verbose, out.Results.line},
%! {"file" , "foo" , 80 , true , "circle"});
%! ## check KeepUnmatched
%! p.KeepUnmatched = true;
%! out = p.parse ("file", "foo", 80, "line", "circle", "verbose", "extra", 50);
%!assert (out.Unmatched.extra, 50)
%! ## check error when missing required
%!error(p.parse())
%! ## check error when given required do not validate
%!error(p.parse(50))
%! ## check error when given optional do not validate
%!error(p.parse("file", "no-val"))
%! ## check error when given ParamValue do not validate
%!error(p.parse("file", "foo", 51, "line", "round"))
## check alternative method (obj), ...) API
%!shared p, out
%! p = inputParser;
%! p = addRequired (p, "req1", @(x) ischar (x));
%! p = addOptional (p, "op1", "val", @(x) ischar (x) && any (strcmp (x, {"val", "foo"})));
%! p = addOptional (p, "op2", 78, @(x) (x) > 50);
%! p = addSwitch (p, "verbose");
%! p = addParamValue (p, "line", "tree", @(x) ischar (x) && any (strcmp (x, {"tree", "circle"})));
%! ## check normal use, only required are given
%! out = parse (p, "file");
%!assert ({out.Results.req1, out.Results.op1, out.Results.op2, out.Results.verbose, out.Results.line},
%! {"file" , "val" , 78 , false , "tree"});
%!assert (out.UsingDefaults, {"op1", "op2", "verbose", "line"});
%! ## check normal use, but give values different than defaults
%! out = parse (p, "file", "foo", 80, "line", "circle", "verbose");
%!assert ({out.Results.req1, out.Results.op1, out.Results.op2, out.Results.verbose, out.Results.line},
%! {"file" , "foo" , 80 , true , "circle"});
## if we were matlab compatible...
%!shared p, out
%! p = inputParser;
%! p = p.addOptional ("op1", "val");
%! p = p.addParamValue ("line", "tree");
%!xtest assert (getfield (p.parse("line", "circle"), "Results"), struct ("op1", "val", "line", "circle"));