Mercurial > octave
view scripts/miscellaneous/inputParser.m @ 30564:796f54d4ddbf stable
update Octave Project Developers copyright for the new year
In files that have the "Octave Project Developers" copyright notice,
update for 2021.
In all .txi and .texi files except gpl.txi and gpl.texi in the
doc/liboctave and doc/interpreter directories, change the copyright
to "Octave Project Developers", the same as used for other source
files. Update copyright notices for 2022 (not done since 2019). For
gpl.txi and gpl.texi, change the copyright notice to be "Free Software
Foundation, Inc." and leave the date at 2007 only because this file
only contains the text of the GPL, not anything created by the Octave
Project Developers.
Add Paul Thomas to contributors.in.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Tue, 28 Dec 2021 18:22:40 -0500 |
parents | 363fb10055df |
children | 2c8ab613e805 |
line wrap: on
line source
######################################################################## ## ## Copyright (C) 2011-2022 The Octave Project Developers ## ## See the file COPYRIGHT.md in the top-level directory of this ## distribution or <https://octave.org/copyright/>. ## ## This file is part of Octave. ## ## Octave 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. ## ## Octave 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 Octave; see the file COPYING. If not, see ## <https://www.gnu.org/licenses/>. ## ######################################################################## classdef inputParser < handle ## -*- texinfo -*- ## @deftypefn {} {@var{p} =} inputParser () ## Create object @var{p} of the inputParser class. ## ## This class is designed to allow easy parsing of function arguments. The ## class supports four types of arguments: ## ## @enumerate ## @item mandatory (see @code{addRequired}); ## ## @item optional (see @code{addOptional}); ## ## @item named (see @code{addParameter}); ## ## @item switch (see @code{addSwitch}). ## @end enumerate ## ## After defining the function API with these methods, the supplied arguments ## can be parsed with the @code{parse} method and the parsing results ## accessed with the @code{Results} accessor. ## @end deftypefn ## ## @deftypefn {} {} inputParser.Parameters ## Return list of parameter names already defined. ## @end deftypefn ## ## @deftypefn {} {} inputParser.Results ## Return structure with argument names as fieldnames and corresponding ## values. ## @end deftypefn ## ## @deftypefn {} {} inputParser.Unmatched ## Return structure similar to @code{Results}, but for unmatched parameters. ## See the @code{KeepUnmatched} property. ## @end deftypefn ## ## @deftypefn {} {} inputParser.UsingDefaults ## Return cell array with the names of arguments that are using default ## values. ## @end deftypefn ## ## @deftypefn {} {} inputParser.CaseSensitive = @var{boolean} ## Set whether matching of argument names should be case sensitive. Defaults ## to false. ## @end deftypefn ## ## @deftypefn {} {} inputParser.FunctionName = @var{name} ## Set function name to be used in error messages; Defaults to empty string. ## @end deftypefn ## ## @deftypefn {} {} inputParser.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 ## @code{Switch} and @code{Parameter} 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 @code{Parameter} type and it is expected to ## be followed by a value. ## @end deftypefn ## ## @deftypefn {} {} inputParser.StructExpand = @var{boolean} ## Set whether a structure can be passed to the function instead of ## parameter/value pairs. Defaults to true. ## ## The following example shows how to use this class: ## ## @example ## function check (varargin) ## @c The next two comments need to be indented by one for alignment ## p = inputParser (); # create object ## p.FunctionName = "check"; # set function name ## p.addRequired ("pack", @@ischar); # mandatory argument ## p.addOptional ("path", pwd(), @@ischar); # optional argument ## ## ## create a function handle to anonymous functions for validators ## val_mat = @@(x) isvector (x) && all (x <= 1) && all (x >= 0); ## p.addOptional ("mat", [0 0], val_mat); ## ## ## create two arguments of type "Parameter" ## val_type = @@(x) any (strcmp (x, @{"linear", "quadratic"@})); ## p.addParameter ("type", "linear", val_type); ## val_verb = @@(x) any (strcmp (x, @{"low", "medium", "high"@})); ## p.addParameter ("tolerance", "low", val_verb); ## ## ## create a switch type of argument ## p.addSwitch ("verbose"); ## ## p.parse (varargin@{:@}); # Run created parser on inputs ## ## ## the rest of the function can access inputs by using p.Results. ## ## for example, get the tolerance input with p.Results.tolerance ## endfunction ## @end example ## ## @example ## @group ## check ("mech"); # valid, use defaults for other arguments ## check (); # error, one argument is mandatory ## check (1); # error, since ! ischar ## check ("mech", "~/dev"); # valid, use defaults for other arguments ## ## check ("mech", "~/dev", [0 1 0 0], "type", "linear"); # valid ## ## ## following is also valid. Note how the Switch argument type can ## ## be mixed into or before the Parameter argument type (but it ## ## must still appear after any Optional argument). ## check ("mech", "~/dev", [0 1 0 0], "verbose", "tolerance", "high"); ## ## ## 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. @code{Required} arguments must be ## first and can be followed by any @code{Optional} arguments. Only ## the @code{Parameter} and @code{Switch} arguments may be mixed ## together and they must appear at the end. ## ## @emph{Note 2}: If both @code{Optional} and @code{Parameter} arguments ## are mixed in a function API then once a string Optional argument fails to ## validate it will be considered the end of the @code{Optional} ## arguments. The remaining arguments will be compared against any ## @code{Parameter} or @code{Switch} arguments. ## ## @seealso{nargin, validateattributes, validatestring, varargin} ## @end deftypefn properties ## FIXME: set input checking for these properties CaseSensitive = false; FunctionName = ""; KeepUnmatched = false; PartialMatching = false; # FIXME: unimplemented (and default should be true) StructExpand = true; endproperties properties (SetAccess = protected) Parameters = cell (); Results = struct (); Unmatched = struct (); UsingDefaults = cell (); endproperties properties (Access = protected) ## Since Required and Optional are ordered, they get a cell array of ## structs with the fields "name", "def" (default), and "val" (validator). Required = cell (); Optional = cell (); ## Parameter and Switch are unordered so we have a struct whose fieldnames ## are the argname, and values are a struct with fields "def" and "val" Parameter = struct (); Switch = struct (); ## List of Parameter and Switch names to ease searches ParameterNames = cell (); SwitchNames = cell (); ## When checking for fieldnames in a Case Insensitive way, this variable ## holds the correct identifier for the last searched named using the ## is_argname method. last_name = ""; endproperties properties (Access = protected, Constant = true) ## Default validator, always returns scalar true. def_val = @(~) true; endproperties methods function set.PartialMatching (this, val) if (val) error ("inputParser: PartialMatching is not yet implemented"); endif endfunction function addRequired (this, name, val = inputParser.def_val) ## -*- texinfo -*- ## @deftypefn {} {} addRequired (@var{argname}) ## @deftypefnx {} {} addRequired (@var{argname}, @var{validator}) ## Add new mandatory argument to the object @var{parser} of inputParser ## class. This method belongs to the inputParser class and implements ## an ordered arguments type of API. ## ## @var{argname} must be a string with the name of the new argument. The ## order in which new arguments are added with @code{addrequired}, ## represents the expected order of arguments. ## ## @var{validator} is an optional function handle to validate the given ## values for the argument with name @var{argname}. Alternatively, a ## function name can be used. ## ## See @code{help inputParser} for examples. ## ## @emph{Note}: this can be used together with the other type of ## arguments but it must be the first (see @code{@@inputParser}). ## ## @end deftypefn if (nargin < 2) print_usage (); elseif (numel (this.Optional) || numfields (this.Parameter) || numfields (this.Switch)) error (["inputParser.addRequired: can't have a Required argument " ... "after Optional, Parameter, or Switch"]); endif this.validate_name ("Required", name); this.Required{end+1} = struct ("name", name, "val", val); endfunction function addOptional (this, name, def, val = inputParser.def_val) ## -*- texinfo -*- ## @deftypefn {} {} addOptional (@var{argname}, @var{default}) ## @deftypefnx {} {} addOptional (@var{argname}, @var{default}, @var{validator}) ## Add new optional argument to the object @var{parser} of the class ## inputParser to implement an ordered arguments type of API ## ## @var{argname} must be a string with the name of the new argument. The ## order in which new arguments are added with @code{addOptional}, ## represents the expected order of arguments. ## ## @var{default} will be the value used when the argument is not ## specified. ## ## @var{validator} is an optional anonymous function to validate the ## given values for the argument with name @var{argname}. Alternatively, ## a function name can be used. ## ## See @code{help inputParser} for examples. ## ## @emph{Note}: if a string argument does not validate, it will be ## considered a ParamValue key. If an optional argument is not given a ## validator, anything will be valid, and so any string will be ## considered will be the value of the optional argument (in @sc{matlab}, ## if no validator is given and argument is a string it will also be ## considered a ParamValue key). ## ## @end deftypefn if (nargin < 3) print_usage (); elseif (numfields (this.Parameter) || numfields (this.Switch)) error (["inputParser.Optional: can't have Optional arguments " ... "after Parameter or Switch"]); endif this.validate_name ("Optional", name); this.Optional{end+1} = struct ("name", name, "def", def, "val", val); endfunction function addParamValue (this, name, def, val = inputParser.def_val) ## -*- texinfo -*- ## @deftypefn {} {} addParamValue (@var{argname}, @var{default}) ## @deftypefnx {} {} addParamValue (@var{argname}, @var{default}, @var{validator}) ## Add new parameter to the object @var{parser} of the class inputParser ## to implement a name/value pair type of API. ## ## This is an alias for @code{addParameter} method without the ## @qcode{"PartialMatchPriority"} option. See it for the help text. ## ## @end deftypefn if (nargin < 3) print_usage (); endif this.addParameter (name, def, val); endfunction function addParameter (this, name, def, varargin) ## -*- texinfo -*- ## @deftypefn {} {} addParameter (@var{argname}, @var{default}) ## @deftypefnx {} {} addParameter (@var{argname}, @var{default}, @var{validator}) ## Add new parameter to the object @var{parser} of the class inputParser ## to implement a name/value pair type of API. ## ## @var{argname} must be a string with the name of the new parameter. ## ## @var{default} will be the value used when the parameter is not ## specified. ## ## @var{validator} is an optional function handle to validate the given ## values for the parameter with name @var{argname}. Alternatively, a ## function name can be used. ## ## See @code{help inputParser} for examples. ## ## @end deftypefn if (nargin < 3 || nargin > 6) print_usage (); endif n_opt = numel (varargin); if (n_opt == 0 || n_opt == 2) val = inputParser.def_val; else # n_opt is 1 or 3 val = varargin{1}; endif if (n_opt == 0 || n_opt == 1) match_priority = 1; else # n_opt is 2 or 3 if (! strcmpi (varargin{end-1}, "PartialMatchPriority")) error ("inputParser.addParameter: unrecognized option"); endif match_priority = varargin{end}; validateattributes (match_priority, {"numeric"}, {"positive", "integer"}, "inputParser.addParameter", "PartialMatchPriority"); endif this.validate_name ("Parameter", name); this.Parameter.(name).def = def; this.Parameter.(name).val = val; endfunction function addSwitch (this, name) ## -*- texinfo -*- ## @deftypefn {} {} addSwitch (@var{argname}) ## Add new switch type of argument to the object @var{parser} of ## inputParser class. ## ## This method belongs to the inputParser class and implements a switch ## arguments type of API. ## ## @var{argname} must be a string with the name of the new argument. ## Arguments of this type can be specified at the end, after ## @code{Required} and @code{Optional}, and mixed between the ## @code{Parameter}. They default to false. If one of the arguments ## supplied is a string like @var{argname}, then after parsing the value ## of @var{parse}.Results.@var{argname} will be true. ## ## See @code{help inputParser} for examples. ## ## @end deftypefn if (nargin != 2) print_usage (); endif this.validate_name ("Switch", name); this.Switch.(name).def = false; endfunction function parse (this, varargin) ## -*- texinfo -*- ## @deftypefn {} {} parse (@var{varargin}) ## Parses and validates list of arguments according to object ## @var{parser} of the class inputParser. ## ## After parsing, the results can be accessed with the @code{Results} ## accessor. See @code{help inputParser} for a more complete ## description. ## ## @end deftypefn this.Results = struct (); this.Unmatched = struct (); this.UsingDefaults = cell (); if (numel (varargin) < numel (this.Required)) if (this.FunctionName) print_usage (this.FunctionName); else this.error ("inputParser.parse: not enough input arguments"); endif endif pnargin = numel (varargin); this.ParameterNames = fieldnames (this.Parameter); this.SwitchNames = fieldnames (this.Switch); ## Evaluate the Required arguments first nReq = numel (this.Required); for idx = 1:nReq req = this.Required{idx}; this.validate_arg (req.name, req.val, varargin{idx}); endfor vidx = nReq; # current index in varargin ## Search for a list of Optional arguments idx = 0; # current index on the array of Optional nOpt = numel (this.Optional); while (vidx < pnargin && idx < nOpt) opt = this.Optional{++idx}; in = varargin{++vidx}; if ((this.is_argname ("Parameter", in) && vidx < pnargin) || this.is_argname ("Switch", in)) ## This looks like an optional parameter/value pair or a ## switch, not an positional option. This does mean that ## positional options cannot be strings named like parameter ## keys. See bug #50752. idx -= 1; vidx -= 1; break; endif try valid_option = opt.val (in); catch valid_option = false; end_try_catch if (! valid_option) ## If it does not match there's two options: ## 1) input is actually wrong and we should error; ## 2) it's a Parameter or Switch name and we should use ## the default for the rest; ## 3) it's a struct with the Parameter pairs. if (ischar (in) || (this.StructExpand && isstruct (in) && isscalar (in))) idx -= 1; vidx -= 1; break; else this.error (sprintf (["failed validation of %s\n", ... "Validation function: %s"], toupper (opt.name), disp (opt.val))); endif endif this.Results.(opt.name) = in; endwhile ## Fill in with defaults of missing Optional while (idx++ < nOpt) opt = this.Optional{idx}; this.UsingDefaults{end+1} = opt.name; this.Results.(opt.name) = opt.def; endwhile ## Search unordered Options (Switch and Parameter) while (vidx++ < pnargin) name = varargin{vidx}; if (this.StructExpand && isstruct (name) && isscalar (name)) expanded_options = [fieldnames(name) struct2cell(name)]'(:); if (isempty (expanded_options)) continue; # empty, continue to next argument endif n_new_args = numel (expanded_options) -1; pnargin += n_new_args; varargin(vidx+n_new_args+1:pnargin) = varargin(vidx+1:end); varargin(vidx:vidx+n_new_args) = expanded_options; name = varargin{vidx}; endif if (! ischar (name)) this.error ("non-string for Parameter name or Switch"); endif if (this.is_argname ("Parameter", name)) if (vidx++ > pnargin) this.error (sprintf ("no matching value for option '%s'", toupper (name))); endif this.validate_arg (this.last_name, this.Parameter.(this.last_name).val, varargin{vidx}); elseif (this.is_argname ("Switch", name)) this.Results.(this.last_name) = true; else if (vidx++ < pnargin && this.KeepUnmatched) this.Unmatched.(name) = varargin{vidx}; else this.error (sprintf ("argument '%s' is not a valid parameter", toupper (name))); endif endif endwhile ## Add them to the UsingDefaults list this.add_missing ("Parameter"); this.add_missing ("Switch"); endfunction function disp (this) if (nargin != 1) print_usage (); endif printf ("inputParser object with properties:\n\n"); b2s = @(x) ifelse (any (x), "true", "false"); printf ([" CaseSensitive : %s\n FunctionName : %s\n" ... " KeepUnmatched : %s\n PartialMatching : %s\n" ... " StructExpand : %s\n\n"], b2s (this.CaseSensitive), b2s (this.FunctionName), b2s (this.KeepUnmatched), b2s (this.PartialMatching), b2s (this.StructExpand)); printf ("Defined parameters:\n\n {%s}\n", strjoin (this.Parameters, ", ")); endfunction endmethods methods (Access = private) function validate_name (this, type, name) if (! isvarname (name)) error ("inputParser.add%s: NAME is an invalid identifier", method); elseif (any (strcmpi (this.Parameters, name))) ## Even if CaseSensitive is "on", we still shouldn't allow ## two args with the same name. error ("inputParser.add%s: argname '%s' has already been specified", type, name); endif this.Parameters{end+1} = name; endfunction function validate_arg (this, name, val, in) if (! val (in)) this.error (sprintf ("failed validation of %s with %s", toupper (name), func2str (val))); endif this.Results.(name) = in; endfunction function r = is_argname (this, type, name) r = ischar (name) && isrow (name); if (r) if (this.CaseSensitive) r = isfield (this.(type), name); if (r) this.last_name = name; endif else fnames = this.([type "Names"]); l = strcmpi (name, fnames); r = any (l(:)); if (r) this.last_name = fnames{l}; endif endif endif endfunction function add_missing (this, type) unmatched = setdiff (fieldnames (this.(type)), fieldnames (this.Results)); for namec = unmatched(:)' name = namec{1}; this.UsingDefaults{end+1} = name; this.Results.(name) = this.(type).(name).def; endfor endfunction function error (this, msg) where = ""; if (this.FunctionName) where = [this.FunctionName ": "]; endif error ("%s%s", where, msg); endfunction endmethods endclassdef %!function p = create_p () %! p = inputParser (); %! p.CaseSensitive = true; %! p.addRequired ("req1", @(x) ischar (x)); %! p.addOptional ("op1", "val", @(x) any (strcmp (x, {"val", "foo"}))); %! p.addOptional ("op2", 78, @(x) x > 50); %! p.addSwitch ("verbose"); %! p.addParameter ("line", "tree", @(x) any (strcmp (x, {"tree", "circle"}))); %!endfunction ## check normal use, only required are given %!test %! p = create_p (); %! p.parse ("file"); %! r = p.Results; %! assert (r.req1, "file"); %! assert (sort (p.UsingDefaults), sort ({"op1", "op2", "verbose", "line"})); %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line}, %! {"file", "val", 78, false, "tree"}); ## check normal use, but give values different than defaults %!test %! p = create_p (); %! p.parse ("file", "foo", 80, "line", "circle", "verbose"); %! r = p.Results; %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line}, %! {"file", "foo", 80, true, "circle"}); ## check optional is skipped and considered Parameter if unvalidated string %!test %! p = create_p (); %! p.parse ("file", "line", "circle"); %! r = p.Results; %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line}, %! {"file", "val", 78, false, "circle"}); ## check case insensitivity %!test %! p = create_p (); %! p.CaseSensitive = false; %! p.parse ("file", "foo", 80, "LiNE", "circle", "vERbOSe"); %! r = p.Results; %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line}, %! {"file", "foo", 80, true, "circle"}); ## check KeepUnmatched %!test %! p = create_p (); %! p.KeepUnmatched = true; %! p.parse ("file", "foo", 80, "line", "circle", "verbose", "extra", 50); %! assert (p.Unmatched.extra, 50); ## check error when missing required %!error <not enough input arguments> %! p = create_p (); %! p.parse (); ## check error when given required does not validate %!error <failed validation of > %! p = create_p (); %! p.parse (50); ## check error when given optional does not validate %!error <is not a valid parameter> %! p = create_p (); %! p.parse ("file", "no-val"); ## check error when given Parameter does not validate %!error <failed validation of > %! p = create_p (); %! p.parse ("file", "foo", 51, "line", "round"); ## check alternative method (obj, ...) API %!function p2 = create_p2 (); %! p2 = inputParser (); %! addRequired (p2, "req1", @(x) ischar (x)); %! addOptional (p2, "op1", "val", @(x) any (strcmp (x, {"val", "foo"}))); %! addOptional (p2, "op2", 78, @(x) x > 50); %! addSwitch (p2, "verbose"); %! addParameter (p2, "line", "tree", @(x) any (strcmp (x, {"tree", "circle"}))); %!endfunction ## check normal use, only required are given %!test %! p2 = create_p2 (); %! parse (p2, "file"); %! r = p2.Results; %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line}, %! {"file", "val", 78, false, "tree"}); %! assert (sort (p2.UsingDefaults), sort ({"op1", "op2", "verbose", "line"})); ## check normal use, but give values different than defaults %!test %! p2 = create_p2 (); %! parse (p2, "file", "foo", 80, "line", "circle", "verbose"); %! r = p2.Results; %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line}, %! {"file", "foo", 80, true, "circle"}); ## We must not perform validation of default values %!test <*45837> %! p = inputParser (); %! p.addParameter ("Dir", [], @ischar); %! p.parse (); %! assert (p.Results.Dir, []); %!test %! p = inputParser (); %! p.addParameter ("positive", -1, @(x) x > 5); %! p.parse (); %! assert (p.Results.positive, -1); ## Throw an error on validation of optional argument to check that it ## is caught without preventing continuation into param/value pairs. %!test %! p = inputParser (); %! p.addOptional ("err", "foo", @error); %! p.addParameter ("not_err", "bar", @ischar); %! p.parse ("not_err", "qux"); %! assert (p.Results.err, "foo"); %! assert (p.Results.not_err, "qux"); ## With more Parameters to test StructExpand %!function p3 = create_p3 (); %! p3 = inputParser (); %! addOptional (p3, "op1", "val", @(x) any (strcmp (x, {"val", "foo"}))); %! addOptional (p3, "op2", 78, @(x) x > 50); %! addSwitch (p3, "verbose"); %! addParameter (p3, "line", "tree", @(x) any (strcmp (x, {"tree", "circle"}))); %! addParameter (p3, "color", "red", @(x) any (strcmp (x, {"red", "green"}))); %! addParameter (p3, "style", "tt", @(x) any (strcmp (x, {"tt", "f", "i"}))); %!endfunction ## Test StructExpand %!test %! p3 = create_p3 (); %! p3.parse (struct ("line", "circle", "color", "green")); %! assert (p3.Results, struct ("op1", "val", "op2", 78, "verbose", false, %! "line", "circle", "color", "green", %! "style", "tt")) %!test %! p3 = create_p3 (); %! p3.parse (struct ("line", "circle", "color", "green"), "line", "tree"); %! assert (p3.Results.line, "tree"); %! p3.parse ("line", "tree", struct ("line", "circle", "color", "green")); %! assert (p3.Results.line, "circle"); %!test # unmatched parameters with StructExpand %! p3 = create_p3 (); %! p3.KeepUnmatched = true; %! p3.parse (struct ("line", "circle", "color", "green", "bar", "baz")); %! assert (p3.Unmatched.bar, "baz"); ## The validation for the second optional argument throws an error with ## a struct so check that we can handle it. %!test %! p3 = create_p3 (); %! p3.parse ("foo", struct ("color", "green"), "line", "tree"); %! assert (p3.Results.op1, "foo"); %! assert (p3.Results.line, "tree"); %! assert (p3.Results.color, "green"); %! assert (p3.Results.verbose, false); ## Some simple tests for addParamValue since all the other ones use add ## addParameter but they use the same codepath. %!test %! p = inputParser (); %! addParameter (p, "line", "tree", @(x) any (strcmp (x, {"tree", "circle"}))); %! addParameter (p, "color", "red", @(x) any (strcmp (x, {"red", "green"}))); %! p.parse ("line", "circle"); %! assert ({p.Results.line, p.Results.color}, {"circle", "red"}); %!test %! p = inputParser (); %! p.addParameter ("foo", "bar", @ischar); %! p.parse (); %! assert (p.Results, struct ("foo", "bar")); %! p.parse ("foo", "qux"); %! assert (p.Results, struct ("foo", "qux")); ## This behaviour means that a positional option can never be a string ## that is the name of a parameter key. This is required for Matlab ## compatibility. %!test <*50752> %! p = inputParser (); %! p.addOptional ("op1", "val"); %! p.addParameter ("line", "tree"); %! p.parse ("line", "circle"); %! assert (p.Results, struct ("op1", "val", "line", "circle")); %! %! p = inputParser (); %! p.addOptional ("op1", "val1"); %! p.addOptional ("op2", "val2"); %! p.addParameter ("line", "tree"); %! p.parse ("line", "circle"); %! assert (p.Results.op1, "val1"); %! assert (p.Results.op2, "val2"); %! assert (p.Results.line, "circle"); %! %! ## If there's enough arguments to fill the positional options and %! ## param/key, it still skips positional options. %! p = inputParser (); %! p.addOptional ("op1", "val1"); %! p.addOptional ("op2", "val2"); %! p.addParameter ("line", "tree"); %! p.parse ("line", "circle", "line", "rectangle"); %! assert (p.Results, struct ("op1", "val1", "op2", "val2", %! "line", "rectangle")) %! %! ## Even if the key/param fails validation, it does not backtrack to %! ## check if the values are valid positional options. %! p = inputParser (); %! p.addOptional ("op1", "val1", @ischar); %! p.addOptional ("op2", "val2", @isnumeric); %! p.addParameter ("line", "circle", @ischar); %! fail ('p.parse ("line", 89)', "failed validation of LINE") %! %! p = inputParser (); %! p.addOptional ("op1", "val1"); %! p.addParamValue ("line", "circle", @ischar); %! fail ('p.parse ("line", "line", 89)', %! "non-string for Parameter name or Switch") %!test <*50752> %! ## This fails in Matlab but works in Octave. It is a bug there %! ## that we do not replicate. %! p = inputParser (); %! p.addOptional ("op1", "val1"); %! p.addParameter ("line", "circle"); %! p.parse ("line"); %! assert (p.Results, struct ("op1", "line", "line", "circle")); %!test <*50752> %! p = inputParser (); %! p.addOptional ("op1", "val1"); %! p.addSwitch ("line"); %! p.parse ("line"); %! assert (p.Results.op1, "val1"); %! assert (p.Results.line, true); %!test %! p = inputParser (); %! p.addParameter ("a", []); %! p.addParameter ("b", []); %! p.parse ("a", 1); %! p.parse ("b", 1); %! assert (p.Results, struct ("a", [], "b", 1)); %! assert (p.UsingDefaults, {"a"}); %!test %! p = inputParser (); %! p.addParameter ("b", []); %! p.KeepUnmatched = true; %! p.parse ("a", 1); %! p.parse ("b", 1); %! assert (p.Results, struct ("b", 1)); %! assert (p.Unmatched, struct ()); ## Test for patch #9241 %!error <failed validation of A with ischar> %! p = inputParser (); %! p.addParameter ("a", [], @ischar); %! p.parse ("a", 1); %!test <*58112> %! p = inputParser (); %! p.addRequired ("first"); %! p.addOptional ("second", []); %! p.parse (1, {"test", 1, 2, 3}); %! r = p.Results; %! assert (r.first, 1); %! assert (r.second, {"test", 1, 2, 3});