changeset 32035:80fef9315743

inputParser.m: Implement "PartialMatching" property (bug #64050) * NEWS.9.md: Announce change in Matlab Compatibility section. * inputParser.m: Document "PartialMatching" property. Small tweaks in rest of docstring. * inputParser.m (inputParser): Change PartialMatching property default to true. * inputParser.m (set.PartialMatching): Delete function. * inputParser.m (addParamValue): Use similar documentation language. * inputParser.m (addSwitch): Rewrite documentation. * inputParser.m (is_argname): Rewrite to use strncmp when PartialMatching is true and strcmp when false. * inputParser.m: Add BIST tests for PartialMatching.
author Rik <rik@octave.org>
date Tue, 18 Apr 2023 13:43:08 -0700
parents 43e4c7f59889
children c5c065221a0e
files etc/NEWS.9.md scripts/miscellaneous/inputParser.m
diffstat 2 files changed, 92 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/etc/NEWS.9.md	Tue Apr 18 11:26:31 2023 -0700
+++ b/etc/NEWS.9.md	Tue Apr 18 13:43:08 2023 -0700
@@ -33,6 +33,11 @@
 
 ### Matlab compatibility
 
+- The `inputParser` function now implements the `PartialMatching` property
+for `Parameter` and `Switch` names.  For Matlab compatibility,
+`PartialMatching` is now set to true by default which may change the behavior
+of existing code.
+
 - Overhauled `mean`, `median`, `var`, and `std` functions have been imported
 from statistics package v1.5.4 to compatibly implement 'nanflag' (bug #50571),
 'all' (bug #58116), and 'vecdim' (bug #58089) options, preserve output class,
--- a/scripts/miscellaneous/inputParser.m	Tue Apr 18 11:26:31 2023 -0700
+++ b/scripts/miscellaneous/inputParser.m	Tue Apr 18 13:43:08 2023 -0700
@@ -86,9 +86,16 @@
   ## be followed by a value.
   ## @end deftypefn
   ##
+  ## @deftypefn {} {} inputParser.PartialMatching = @var{boolean}
+  ## Set whether argument names for @code{Parameter} and @code{Switch} options
+  ## may be given in shortened form as long as the name uniquely identifies
+  ## an option; Defaults to true.  For example, the argument @qcode{'opt'} will
+  ## match a parameter @qcode{'opt_color'}, but will fail if there is also a
+  ## parameter @qcode{'opt_case'}.
+  ## 
   ## @deftypefn {} {} inputParser.StructExpand = @var{boolean}
   ## Set whether a structure can be passed to the function instead of
-  ## parameter/value pairs.  Defaults to true.
+  ## parameter/value pairs; Defaults to true.
   ##
   ## The following example shows how to use this class:
   ##
@@ -144,7 +151,7 @@
   ## 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.
+  ## together and they must appear following the first two types.
   ##
   ## @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
@@ -157,10 +164,10 @@
 
   properties
     ## FIXME: set input checking for these properties
-    CaseSensitive = false;
-    FunctionName  = "";
-    KeepUnmatched = false;
-    PartialMatching = false; # FIXME: unimplemented (and default should be true)
+    CaseSensitive   = false;
+    FunctionName    = "";
+    KeepUnmatched   = false;
+    PartialMatching = true;
     StructExpand    = true;
   endproperties
 
@@ -198,12 +205,6 @@
 
   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 -*-
@@ -295,7 +296,8 @@
       ## 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.
+      ## @qcode{"PartialMatchPriority"} option.  See @code{addParameter} for
+      ## the help text.
       ##
       ## @end deftypefn
 
@@ -311,8 +313,8 @@
       ## -*- 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.
+      ## Add new parameter argument 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.
       ##
@@ -361,17 +363,17 @@
 
       ## -*- 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.
+      ## Add new switch argument to the object @var{parser} of the class
+      ## inputParser to implement a name/boolean 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
+      ##
+      ## Arguments of this type must be specified after @code{Required} and
+      ## @code{Optional} arguments, but can be mixed with any @code{Parameter}
+      ## definitions.  The default for switch arguments is false.  During
+      ## parsing, If one of the arguments supplied is a string such as
+      ## @var{argname} that matches a defined switch such as
+      ## @code{addSwitch{@var{argname}}}, then after parsing the value
       ## of @var{parse}.Results.@var{argname} will be true.
       ##
       ## See @code{help inputParser} for examples.
@@ -413,7 +415,6 @@
         endif
       endif
       pnargin = numel (varargin);
-
       this.ParameterNames = fieldnames (this.Parameter);
       this.SwitchNames    = fieldnames (this.Switch);
 
@@ -588,21 +589,42 @@
 
       r = ischar (name) && isrow (name);
       if (r)
-        if (this.CaseSensitive)
-          r = isfield (this.(type), name);
-          if (r)
-            this.last_name = name;
+        fnames = this.([type "Names"]);
+        if (this.PartialMatching)
+          if (this.CaseSensitive)
+            idx = strncmp (name, fnames, numel (name));
+            r = sum (idx);
+            if (r > 1)
+              matches = sprintf ("'%s', ", fnames{idx})(1:end-1);
+              matches(end) = '.';
+              this.error (sprintf ("argument '%s' matches more than one %s: %s", name, type, matches));
+            endif
+            r = logical (r);
+          else
+            idx = strncmpi (name, fnames, numel (name));
+            r = sum (idx);
+            if (r > 1)
+              matches = sprintf ("'%s', ", fnames{idx})(1:end-1);
+              matches(end) = '.';
+              this.error (sprintf ("argument '%s' matches more than one %s: %s", name, type, matches));
+            endif
+            r = logical (r);
           endif
         else
-          fnames = this.([type "Names"]);
-          l = strcmpi (name, fnames);
-          r = any (l(:));
-          if (r)
-            this.last_name = fnames{l};
+          if (this.CaseSensitive)
+            idx = strcmp (name, fnames);
+            r = any (idx(:));
+          else
+            idx = strcmpi (name, fnames);
+            r = any (idx(:));
           endif
         endif
       endif
 
+      if (r)
+        this.last_name = fnames{idx};
+      endif
+
     endfunction
 
     function add_missing (this, type)
@@ -634,6 +656,7 @@
 %!function p = create_p ()
 %!  p = inputParser ();
 %!  p.CaseSensitive = true;
+%!  p.PartialMatching = false;
 %!  p.addRequired ("req1", @(x) ischar (x));
 %!  p.addOptional ("op1", "val", @(x) any (strcmp (x, {"val", "foo"})));
 %!  p.addOptional ("op2", 78, @(x) x > 50);
@@ -676,6 +699,15 @@
 %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
 %!         {"file", "foo", 80,    true,      "circle"});
 
+## check PartialMatching (default true state)
+%!test
+%! p = create_p ();
+%! p.PartialMatching = true;
+%! p.parse ("file", "foo", 80, "l", "circle", "verb");
+%! 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 ();
@@ -703,6 +735,23 @@
 %! p = create_p ();
 %! p.parse ("file", "foo", 51, "line", "round");
 
+## check PartialMatching errors
+%!error <'arg' matches more than one Parameter: 'arg123', 'arg456'>
+%! p = inputParser ();
+%! p.addParameter ('arg123', 123);
+%! p.addParameter ('arg456', 456);
+%! p.addSwitch ('arg789');
+%! p.addSwitch ('arg790');
+%! p.parse ('arg', 'bad');
+
+%!error <'arg7' matches more than one Switch: 'arg789', 'arg790'>
+%! p = inputParser ();
+%! p.addParameter ('arg123', 123);
+%! p.addParameter ('arg456', 456);
+%! p.addSwitch ('arg789');
+%! p.addSwitch ('arg790');
+%! p.parse ('arg7', 'bad');
+
 ## check alternative method (obj, ...) API
 %!function p2 = create_p2 ();
 %!  p2 = inputParser ();
@@ -730,7 +779,7 @@
 %! assert ({r.req1, r.op1, r.op2, r.verbose, r.line},
 %!         {"file", "foo", 80,    true,      "circle"});
 
-## We must not perform validation of default values
+## Octave must not perform validation of default values
 %!test <*45837>
 %! p = inputParser ();
 %! p.addParameter ("Dir", [], @ischar);
@@ -944,4 +993,6 @@
 %! p.parse ('Jim', 1980, 'color', 'black');
 %! assert (p.Results.name, 'Jim');
 %! assert (p.Results.year, 1980);
-%! #assert (p.Results.color, 'black');
+%! assert (p.Results.color, 'black');
+
+