]> Creatis software - CreaPhase.git/blobdiff - 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
diff --git a/octave_packages/general-1.3.1/@inputParser/subsref.m b/octave_packages/general-1.3.1/@inputParser/subsref.m
new file mode 100644 (file)
index 0000000..86bd761
--- /dev/null
@@ -0,0 +1,336 @@
+## 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/>.
+
+function inPar = subsref (inPar, idx)
+
+  if ( !isa (inPar, 'inputParser') )
+    error ("object must be of the inputParser class but '%s' was used", class (inPar) );
+  elseif ( idx(1).type != '.' )
+    error ("invalid index for class %s", class (inPar) );
+  endif
+
+  ## the following at the end may allow to use the obj.method notation one day
+  ## jwe is very against this ugly hack
+  ## what would happen if the user has the obj inside a struct? Bad things!
+#  ori = inputname(1);
+#  assignin('caller', ori, inPar);
+
+  method = idx(1).subs;
+
+  switch method
+  case 'Results'
+    inPar = retrieve_results (inPar, idx)
+  case 'Parameters'
+    inPar = inPar.Parameters;
+  case 'parse'
+    inPar = parse_args (inPar, idx);
+  case 'Unmatched'
+  case 'UsingDefaults'
+  case {'addOptional', 'addParamValue', 'addRequired', 'addSwitch'}
+    inPar = check_methods (inPar, idx);
+  otherwise
+    error ("invalid index for reference of class %s", class (inPar) );
+  endswitch
+
+  ## TODO we should make inPar an object of the inputParser class again. At
+  ## least after running parse it becomes just a structure again. While that is
+  ## bad, at least allows for easy access to the Results and Unmatched fields
+  ## without extra coding.
+#  inPar = class (inPar, 'inputParser');
+
+endfunction
+
+function out = retrieve_results (inPar, idx)
+
+  if ( numel(idx) != 2 || idx(2).type != '.' )
+    print_usage ("@inputParser/Results");
+  endif
+
+  out = inPar.Results.(idx(2).subs);
+
+endfunction
+
+
+## when parsing options, here's the principle: Required options have to be the
+## first ones. They are followed by Optional if any. In the end come the
+## ParamValue mixed with Switch. Any other order makes no sense
+function inPar = parse_args (inPar, idx)
+
+  ## syntax is inPar.parse (arguments)
+  if ( numel(idx) != 2 || idx(2).type != '()' )
+    print_usage ("@inputParser/parse");
+  endif
+
+  ## this makes it easier to read but may be memory instensive
+  args = idx(2).subs;
+
+  ## make copy of ordered list of Parameters to keep the original intact and readable
+  inPar.copy = inPar.Parameters;
+
+  if ( numel (fieldnames (inPar.Required)) > numel (args) )
+    error("%sNot enough arguments", inPar.FunctionName);
+  endif
+
+  ## we take names out of 'copy' and values out of 'args', evaluate them and
+  ## store them into 'Results'
+  for i = 1 : numel (fieldnames (inPar.Required))
+    [name, inPar.copy] = shift (inPar.copy);
+    [value, args]      = shift (args);
+    if ( !feval (inPar.Required.(name).validator, value) )
+      error_invalid (inPar.FunctionName, name, inPar.Required.(name).validator);
+    endif
+    inPar.Results.(name) = value;
+  endfor
+
+  ## loop a maximum #times of the number of Optional, similarly to the required
+  ## loop. Once ran out of 'args', move their name into usingDefaults, place
+  ## their default values into 'Results', and break
+
+  ## because if an argument is string and does not validate, should be considered
+  ## a ParamValue key
+  found_possible_key = false;
+
+  for i = 1 : numel (fieldnames (inPar.Optional))
+    if ( !numel (args) || found_possible_key)
+      ## loops the number of Optional options minus the number of them already processed
+      for n = 1 : (numel (fieldnames (inPar.Optional)) - i + 1 )
+        [name, inPar.copy]   = shift (inPar.copy);
+        inPar.UsingDefaults  = push (inPar.UsingDefaults, name);
+        inPar.Results.(name) = inPar.Optional.(name).default;
+      endfor
+      break
+    endif
+    [name, inPar.copy] = shift (inPar.copy);
+    [value, args]      = shift (args);
+    if ( !feval (inPar.Optional.(name).validator, value) )
+      if (ischar (value) )
+        ## maybe the other optional are not defined, this can be Paramvalue
+        ## place this one on defaults and go back to the top with note to clean loop
+        inPar.UsingDefaults  = push (inPar.UsingDefaults, name);
+        inPar.Results.(name) = inPar.Optional.(name).default;
+        found_possible_key   = true;
+        args = unshift (args, value);
+        continue
+      else
+        error_invalid (inPar.FunctionName, name, inPar.Optional.(name).validator);
+      endif
+    else
+      inPar.Results.(name) = value;
+    endif
+  endfor
+
+  ## loop a maximum #times of the number of ParamValue, taking pairs of keys and
+  ## values out 'args'. We no longer expect an order so we need the index in
+  ## 'copy' to remove it from there. Once ran out of 'args', move their name
+  ## into usingDefaults, place their default values into 'Results', and break
+  for i = 1 : (numel (fieldnames (inPar.ParamValue)) + numel (fieldnames (inPar.Switch)))
+    if ( !numel (args) )
+      ## loops the number of times left in 'copy' since these are the last type
+      for n = 1 : numel (inPar.copy)
+        [name, inPar.copy]   = shift (inPar.copy);
+        inPar.UsingDefaults  = push (inPar.UsingDefaults, name);
+        if (isfield (inPar.ParamValue, name))
+          inPar.Results.(name) = inPar.ParamValue.(name).default;
+        else
+          inPar.Results.(name) = inPar.Switch.(name).default;
+        endif
+      endfor
+      break
+    endif
+    [key, args] = shift (args);
+    if ( !ischar (key) )
+      error("%sParameter/Switch names must be strings", inPar.FunctionName);
+    endif
+    if (inPar.CaseSensitive)
+      index = find( strcmp(inPar.copy, key));
+    else
+      index = find( strcmpi(inPar.copy, key));
+    endif
+    ## we can't use isfield here to support case insensitive
+    if (any (strcmpi (fieldnames (inPar.Switch), key)))
+      value  = true;
+      method = "Switch";
+    else
+      ## then it must be a ParamValue (even if unmatched), shift its value
+      if (numel (args) < 1)
+        error ("%sparameter '%s' does not have a value", inPar.FunctionName, key);
+      endif
+      [value, args] = shift (args);
+      method = "ParamValue";
+    endif
+
+    ## empty index means no match so either return error or move them into 'Unmatched'
+    if (!isempty (index))
+      [name, inPar.copy] = shift (inPar.copy, index);
+      if ( !feval (inPar.(method).(name).validator, value))
+        error_invalid (inPar.FunctionName, key, inPar.(method).(name).validator);
+      endif
+      ## we use the name shifted from 'copy' instead of the key from 'args' in case
+      ## the key is in the wrong case
+      inPar.Results.(name) = value;
+    elseif (isempty (index) && inPar.KeepUnmatched )
+      inPar.Unmatched.(key) = value;
+      i = i - 1; # this time didn't count, go back one
+    else
+      error ("%sargument '%s' did not match any valid parameter of the parser", inPar.FunctionName, key);
+    endif
+  endfor
+
+  ## if there's leftovers they must be unmatched. Note that some unmatched can
+  ## have already been processed in the ParamValue loop
+  if ( numel (args) && inPar.KeepUnmatched )
+    for i = 1 : ( numel(args) / 2 )
+      [key, args]           = shift (args);
+      [value, args]         = shift (args);
+      inPar.Unmatched.(key) = value;
+    endfor
+  elseif ( numel (args) )
+    error("%sfound unmatched parameters at end of arguments list", inPar.FunctionName);
+  endif
+
+  ## remove copied field, keep it clean
+  inPar = rmfield (inPar, 'copy');
+
+endfunction
+
+
+function inPar = check_methods (inPar, idx)
+
+  ## this makes it easier to read but is more memory intensive?
+  method  = idx(1).subs;
+  args    = idx(2).subs;
+  func    = sprintf ("@inputParser/%s", method);
+
+  if ( idx(2).type != '()' )
+    print_usage (func);
+  endif
+  def_val      = @() true;
+  [name, args] = shift (args);
+  ## a validator is optional but that complicates handling all the parsing with
+  ## few functions and conditions. If not specified @() true will always return
+  ## true. Simply using true is not enough because if the argument is zero it
+  ## return false and if it's too large, takes up memory
+  switch method
+  case {'addOptional', 'addParamValue'}
+    if     ( numel (args) == 1 )
+      args{2} = def_val;
+    elseif ( numel (args) == 2 )
+      args{2} = validate_validator (args{2});
+    else
+      print_usage(func);
+    endif
+    [def, val] = args{:};
+  case {'addRequired'}
+    if     ( numel (args) == 0 )
+      val = def_val;
+    elseif ( numel (args) == 1 )
+      val = validate_validator (args{1});
+    else
+      print_usage(func);
+    endif
+    def = false;
+  case {'addSwitch'}
+    if ( numel (args) == 0 )
+      val = def_val;
+      def = false;
+    else
+      print_usage(func);
+    endif
+  otherwise
+    error ("invalid index for reference of class %s", class (inPar) );
+  endswitch
+
+  inPar = validate_args (method(4:end), inPar, name, val, def);
+
+endfunction
+
+## because we are nice we also support using the name of a function and not only
+## a function handle
+function val = validate_validator (val)
+  if ( ischar (val) )
+    val = str2func (val);
+  elseif ( !isa (val, 'function_handle') )
+    error ("validator must be a function handle or the name of a valid function");
+  end
+endfunction
+
+## to have a single function that handles them all, something must be done with
+## def value, because addRequire does not have those. That's why the order of
+## the last two args is different from the rest and why 'def' has a default value
+function inPar = validate_args (method, inPar, name, val, def = false)
+
+  if ( !strcmp (class (inPar), 'inputParser') )
+    error ("object must be of the inputParser class but '%s' was used", class (inPar) );
+  elseif ( !isvarname (name) )
+    error ("invalid variable name in argname");
+  endif
+
+  ## because the order arguments are specified are the order they are expected,
+  ## can't have ParamValue/Switch before Optional, and Optional before Required
+  n_optional  = numel (fieldnames (inPar.Optional));
+  n_params    = numel (fieldnames (inPar.ParamValue));
+  n_switch    = numel (fieldnames (inPar.Switch));
+  if     ( strcmp (method, 'Required') && ( n_optional || n_params || n_switch) )
+    error ("Can't specify 'Required' arguments after Optional, ParamValue or Switch");
+  elseif ( strcmp (method, 'Optional') && ( n_params || n_switch) )
+    error ("Can't specify 'Optional' arguments after ParamValue or Switch");
+  endif
+
+  ## even if CaseSensitive is turned on, we still shouldn't have two args with
+  ## the same. What if they decide to change in the middle of specifying them?
+  if ( any (strcmpi (inPar.Parameters, name)) )
+    error ("argname '%s' has already been specified", name);
+  else
+    inPar.Parameters = push (inPar.Parameters, name);
+    inPar.(method).(name).default = def;
+    inPar.(method).(name).validator = val;
+  endif
+
+  ## make sure that the given default value is actually valid
+  ## TODO make sure that when using the default, it's only validated once
+  if ( isa (val, 'function_handle') && !strcmpi (method, 'Required') && !feval (val, def) )
+    error ("default value for '%s' failed validation with '%s'", name, func2str (val) );
+  endif
+
+endfunction
+
+## this is just for consistency of error message
+function error_invalid (prefix, name, val)
+  error("%sargument '%s' failed validation %s", prefix, name, func2str (val));
+endfunction
+
+################################################################################
+## very auxiliary functions
+################################################################################
+
+function [out, in] = shift (in, idx = 1)
+  out     = in{idx};
+  in(idx) = [];
+endfunction
+
+function [in] = unshift (in, add)
+  if ( !iscell (add) )
+    add = {add};
+  endif
+  in (numel(add) + 1 : end + numel(add)) = in;
+  in (1:numel(add)) = add;
+endfunction
+
+function [in] = push (in, add)
+  if ( !iscell (add) )
+    add = {add};
+  endif
+  in( end+1 : end+numel(add) ) = add;
+endfunction