X-Git-Url: https://git.creatis.insa-lyon.fr/pubgit/?p=CreaPhase.git;a=blobdiff_plain;f=octave_packages%2Fgeneral-1.3.1%2F%40inputParser%2Fsubsref.m;fp=octave_packages%2Fgeneral-1.3.1%2F%40inputParser%2Fsubsref.m;h=86bd7612abdd8480d8193267d6daa569b834d4d2;hp=0000000000000000000000000000000000000000;hb=c880e8788dfc484bf23ce13fa2787f2c6bca4863;hpb=1705066eceaaea976f010f669ce8e972f3734b05 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 index 0000000..86bd761 --- /dev/null +++ b/octave_packages/general-1.3.1/@inputParser/subsref.m @@ -0,0 +1,336 @@ +## Copyright (C) 2011-2012 Carnë Draug +## +## 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 . + +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