Mercurial > octave
changeset 24491:d8fb16ab0992
maint: move non-numeric m-files from general/ to miscellaneous/ dir.
* scripts/miscellaneous/fieldnames.m, scripts/miscellaneous/grabcode.m,
scripts/miscellaneous/inputParser.m, scripts/miscellaneous/isdir.m,
scripts/miscellaneous/loadobj.m, scripts/miscellaneous/methods.m,
scripts/miscellaneous/nargchk.m, scripts/miscellaneous/narginchk.m,
scripts/miscellaneous/nargoutchk.m, scripts/miscellaneous/nthargout.m,
scripts/miscellaneous/private/__publish_html_output__.m,
scripts/miscellaneous/private/__publish_latex_output__.m,
scripts/miscellaneous/publish.m, scripts/miscellaneous/saveobj.m,
scripts/miscellaneous/validateattributes.m: Moved from scripts/general dir.
* scripts/general/module.mk, scripts/miscellaneous/module.mk:
Update build system.
author | Rik <rik@octave.org> |
---|---|
date | Thu, 28 Dec 2017 16:14:37 -0800 |
parents | e8d0573279b2 |
children | c83e37384b4f |
files | scripts/general/fieldnames.m scripts/general/grabcode.m scripts/general/inputParser.m scripts/general/isdir.m scripts/general/loadobj.m scripts/general/methods.m scripts/general/module.mk scripts/general/nargchk.m scripts/general/narginchk.m scripts/general/nargoutchk.m scripts/general/nthargout.m scripts/general/private/__publish_html_output__.m scripts/general/private/__publish_latex_output__.m scripts/general/publish.m scripts/general/saveobj.m scripts/general/validateattributes.m scripts/miscellaneous/fieldnames.m scripts/miscellaneous/grabcode.m scripts/miscellaneous/inputParser.m scripts/miscellaneous/isdir.m scripts/miscellaneous/loadobj.m scripts/miscellaneous/methods.m scripts/miscellaneous/module.mk scripts/miscellaneous/nargchk.m scripts/miscellaneous/narginchk.m scripts/miscellaneous/nargoutchk.m scripts/miscellaneous/nthargout.m scripts/miscellaneous/private/__publish_html_output__.m scripts/miscellaneous/private/__publish_latex_output__.m scripts/miscellaneous/publish.m scripts/miscellaneous/saveobj.m scripts/miscellaneous/validateattributes.m |
diffstat | 32 files changed, 4151 insertions(+), 4151 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/general/fieldnames.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -## Copyright (C) 2012-2017 Rik Wehbring -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {@var{names} =} fieldnames (@var{struct}) -## @deftypefnx {} {@var{names} =} fieldnames (@var{obj}) -## @deftypefnx {} {@var{names} =} fieldnames (@var{javaobj}) -## @deftypefnx {} {@var{names} =} fieldnames ("@var{javaclassname}") -## Return a cell array of strings with the names of the fields in the -## specified input. -## -## When the input is a structure @var{struct}, the names are the elements of -## the structure. -## -## When the input is an Octave object @var{obj}, the names are the public -## properties of the object. -## -## When the input is a Java object @var{javaobj} or a string containing the -## name of a Java class @var{javaclassname}, the names are the public fields -## (data members) of the object or class. -## @seealso{numfields, isfield, orderfields, struct, methods} -## @end deftypefn - -function names = fieldnames (obj) - - if (nargin != 1) - print_usage (); - endif - - if (isstruct (obj) || isobject (obj)) - ## Call internal C++ function for structs or Octave objects - names = __fieldnames__ (obj); - elseif (isjava (obj) || ischar (obj)) - ## FIXME: Function prototype that accepts java obj exists, but doesn't - ## work if obj is java.lang.String. Convert obj to classname. - ## FIXME: this is now working for objects whose class is in the dynamic - ## classpath but will continue to fail if such classnames are used - ## instead (see bug #42710). - if (isa (obj, "java.lang.String")) - obj = class (obj); - endif - names_str = javaMethod ("getFields", "org.octave.ClassHelper", obj); - names = ostrsplit (names_str, ';'); - else - error ("fieldnames: Invalid input argument"); - endif - -endfunction - - -## test preservation of fieldname order -%!test -%! x(3).d=1; x(2).a=2; x(1).b=3; x(2).c=3; -%! assert (fieldnames (x), {"d"; "a"; "b"; "c"}); - -## test empty structure -%!test -%! s = struct (); -%! assert (fieldnames (s), cell (0, 1)); - -## test Java classname by passing classname -%!testif HAVE_JAVA; usejava ("jvm") -%! names = fieldnames ("java.lang.Double"); -%! assert (any (strcmp (names, "MAX_VALUE"))); - -## test Java classname by passing java object -%!testif HAVE_JAVA; usejava ("jvm") -%! names = fieldnames (javaObject ("java.lang.Double", 10)); -%! assert (any (strcmp (names, "MAX_VALUE"))); -%! assert (all (ismember ({"POSITIVE_INFINITY", "NEGATIVE_INFINITY", ... -%! "NaN", "MAX_VALUE", "MIN_NORMAL", "MIN_VALUE", ... -%! "MAX_EXPONENT", "MIN_EXPONENT", "SIZE", "TYPE"}, -%! names))); - -%!testif HAVE_JAVA; usejava ("jvm") -%! names = fieldnames (javaObject ("java.lang.String", "Hello")); -%! assert (any (strcmp (names, "CASE_INSENSITIVE_ORDER")));
--- a/scripts/general/grabcode.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -## Copyright (C) 2016-2017 Kai T. Ohlhus <k.ohlhus@gmail.com> -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} grabcode (@var{url}) -## @deftypefnx {} {} grabcode (@var{filename}) -## @deftypefnx {} {@var{code_str} =} grabcode (@dots{}) -## -## Grab the code from a report created by the @code{publish} function. -## -## The grabbed code inside the published report must be enclosed by the -## strings @samp{##### SOURCE BEGIN #####} and @samp{##### SOURCE END #####}. -## The @code{publish} function creates this format automatically. -## -## If no return value is requested the code is saved to a temporary file and -## opened in the default editor. NOTE: The temporary file must be saved under -## a new or the code will be lost. -## -## If an output is requested the grabbed code will be returned as string -## @var{code_str}. -## -## Example: -## -## @example -## @group -## publish ("my_script.m"); -## grabcode ("html/my_script.html"); -## @end group -## @end example -## -## The example above publishes @file{my_script.m} to the default location -## @file{html/my_script.html}. Next, the published Octave script is grabbed to -## edit its content in a new temporary file. -## -## @seealso{publish} -## @end deftypefn - -function code_str = grabcode (url) - - if (nargin != 1) - print_usage (); - endif - - if (exist (url) == 2) - ## URL is a local file - oct_code = fileread (url); - else - ## Otherwise, try to read remote URL - [oct_code, success, message] = urlread (url); - if (! success) - error (["grabcode: " message]); - endif - endif - - ## Extract relevant part - oct_code = regexp (oct_code, ... - '##### SOURCE BEGIN #####\n(.*)##### SOURCE END #####', "once", "tokens"); - oct_code = oct_code{1}; - - if (nargout == 1) - code_str = oct_code; - else - ## Open temporary file in editor - fname = [tempname() ".m"]; - fid = fopen (fname, "w"); - if (fid < 0) - error ("grabcode: could not open temporary file"); - endif - fprintf (fid, "%s", oct_code); - fclose (fid); - edit (fname); - warndlg (["grabcode: Make sure to save the temporary file\n\n\t", ... - fname, "\n\nto a location of your choice. ", ... - "Otherwise all grabbed code will be lost!"]); - endif - -endfunction - - -## Test input validation -%!error grabcode () -%!error grabcode (1,2)
--- a/scripts/general/inputParser.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,826 +0,0 @@ -## Copyright (C) 2011-2017 Carnë Draug -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- 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 @command{addRequired}); -## -## @item optional (see @command{addOptional}); -## -## @item named (see @command{addParameter}); -## -## @item switch (see @command{addSwitch}). -## @end enumerate -## -## After defining the function API with these methods, the supplied arguments -## can be parsed with the @command{parse} method and the parsing results -## accessed with the @command{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 @command{Results}, but for unmatched parameters. -## See the @command{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 -## @command{Switch} and @command{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 @command{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 -## @group -## 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 group -## @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. @command{Required} arguments must be -## first and can be followed by any @command{Optional} arguments. Only -## the @command{Parameter} and @command{Switch} arguments may be mixed -## together and they must appear at the end. -## -## @emph{Note 2}: If both @command{Optional} and @command{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 @command{Optional} -## arguments. The remaining arguments will be compared against any -## @command{Parameter} or @command{Switch} arguments. -## -## @seealso{nargin, validateattributes, validatestring, varargin} -## @end deftypefn - -## -*- 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 @command{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 @command{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 - -## -*- 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 @command{help inputParser} for examples. -## -## @end deftypefn - -## -*- 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 @command{addParameter} method without the -## @qcode{"PartialMatchPriority"} option. See it for the help text. -## -## @end deftypefn - -## -*- 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 @command{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 @command{help inputParser} for examples. -## -## @emph{Note}: this can be used together with the other type of arguments but -## it must be the first (see @command{@@inputParser}). -## -## @end deftypefn - -## -*- 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 @command{help inputParser} for examples. -## -## @end deftypefn - -## -*- 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 @command{Results} -## accessor. See @command{help inputParser} for a more complete description. -## -## @end deftypefn - -## Author: Carnë Draug <carandraug@octave.org> - -classdef inputParser < handle - 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) - if (nargin < 2 || nargin > 3) - 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) - if (nargin < 3 || nargin > 4) - 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) - if (nargin < 3 || nargin > 4) - print_usage (); - endif - this.addParameter (name, def, val); - endfunction - - function addParameter (this, name, def, varargin) - 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) - if (nargin != 2) - print_usage (); - endif - this.validate_name ("Switch", name); - this.Switch.(name).def = false; - endfunction - - function parse (this, varargin) - 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)]'(:); - 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) - if (this.CaseSensitive) - r = isfield (this.(type), name); - this.last_name = name; - else - fnames = this.([type "Names"]); - l = strcmpi (name, fnames); - r = any (l(:)); - if (r) - this.last_name = fnames{l}; - 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);
--- a/scripts/general/isdir.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -## Copyright (C) 2004-2017 Alois Schloegl -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} isdir (@var{f}) -## Return true if @var{f} is a directory. -## @seealso{exist, stat, is_absolute_filename, is_rooted_relative_filename} -## @end deftypefn - -function retval = isdir (f) - - if (nargin != 1) - print_usage (); - endif - - ## Exist returns an integer but isdir should return a logical. - retval = (exist (f, "dir") == 7); - -endfunction - - -%!assert (isdir (pwd ())) -%!assert (! isdir ("this is highly unlikely to be a directory name")) - -%!error isdir () -%!error isdir (1, 2)
--- a/scripts/general/loadobj.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -## Copyright (C) 2008-2017 David Bateman -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {@var{b} =} loadobj (@var{a}) -## Method of a class to manipulate an object after loading it from a file. -## -## The function @code{loadobj} is called when the object @var{a} is loaded -## using the @code{load} function. An example of the use of @code{saveobj} -## might be to add fields to an object that don't make sense to be saved. -## For example: -## -## @example -## @group -## function b = loadobj (a) -## b = a; -## b.addmissingfield = addfield (b); -## endfunction -## @end group -## @end example -## -## @seealso{saveobj, class} -## @end deftypefn - -function b = loadobj (a) - error ('loadobj: not defined for class "%s"', class (a)); -endfunction
--- a/scripts/general/methods.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -## Copyright (C) 2012-2017 Rik Wehbring -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} methods (@var{obj}) -## @deftypefnx {} {} methods ("@var{classname}") -## @deftypefnx {} {@var{mtds} =} methods (@dots{}) -## List the names of the public methods for the object @var{obj} or the -## named class @var{classname}. -## -## @var{obj} may be an Octave class object or a Java object. -## @var{classname} may be the name of an Octave class or a Java class. -## -## When called with no output arguments, @code{methods} prints the list of -## method names to the screen. Otherwise, the output argument @var{mtds} -## contains the list in a cell array of strings. -## @seealso{fieldnames} -## @end deftypefn - -function mtds = methods (obj) - - if (nargin != 1) - print_usage (); - endif - - if (isobject (obj)) - ## Call internal C++ function for Octave objects - mtds_list = __methods__ (obj); - elseif (ischar (obj)) - ## Could be a classname for an Octave class or Java class. - ## Try Octave class first. - mtds_list = __methods__ (obj); - if (isempty (mtds_list)) - mtds_str = javaMethod ("getMethods", "org.octave.ClassHelper", obj); - mtds_list = ostrsplit (mtds_str, ';'); - endif - elseif (isjava (obj)) - ## FIXME: Function prototype accepts java obj, but doesn't work if obj - ## is e.g., java.lang.String. Convert obj to classname then. - try - mtds_str = javaMethod ("getMethods", "org.octave.ClassHelper", obj); - catch - obj = class (obj); - mtds_str = javaMethod ("getMethods", "org.octave.ClassHelper", obj); - end_try_catch - mtds_list = strsplit (mtds_str, ';'); - else - error ("methods: Invalid input argument"); - endif - - if (nargout == 0) - classname = ifelse (ischar (obj), obj, class (obj)); - printf ("Methods for class %s:\n", classname); - disp (list_in_columns (mtds_list)); - else - mtds = mtds_list; - endif - -endfunction - - -## test Octave classname -%!test -%! mtds = methods ("ftp"); -%! assert (mtds{1}, "ascii"); - -## test Java classname -%!testif HAVE_JAVA; usejava ("jvm") -%! mtds = methods ("java.lang.Double"); -%! search = strfind (mtds, "java.lang.Double valueOf"); -%! assert (! isempty ([search{:}]));
--- a/scripts/general/module.mk Thu Dec 28 15:56:09 2017 -0800 +++ b/scripts/general/module.mk Thu Dec 28 16:14:37 2017 -0800 @@ -3,8 +3,6 @@ %reldir%/private %canon_reldir%_PRIVATE_FCN_FILES = \ - %reldir%/private/__publish_html_output__.m \ - %reldir%/private/__publish_latex_output__.m \ %reldir%/private/__splinen__.m %canon_reldir%_FCN_FILES = \ @@ -30,15 +28,12 @@ %reldir%/deg2rad.m \ %reldir%/del2.m \ %reldir%/divergence.m \ - %reldir%/fieldnames.m \ %reldir%/flip.m \ %reldir%/flipdim.m \ %reldir%/fliplr.m \ %reldir%/flipud.m \ - %reldir%/grabcode.m \ %reldir%/gradient.m \ %reldir%/idivide.m \ - %reldir%/inputParser.m \ %reldir%/int2str.m \ %reldir%/integral.m \ %reldir%/integral2.m \ @@ -48,23 +43,15 @@ %reldir%/interp3.m \ %reldir%/interpft.m \ %reldir%/interpn.m \ - %reldir%/isdir.m \ %reldir%/isequal.m \ %reldir%/isequaln.m \ - %reldir%/loadobj.m \ %reldir%/logspace.m \ - %reldir%/methods.m \ - %reldir%/nargchk.m \ - %reldir%/narginchk.m \ - %reldir%/nargoutchk.m \ %reldir%/nextpow2.m \ - %reldir%/nthargout.m \ %reldir%/num2str.m \ %reldir%/pol2cart.m \ %reldir%/polyarea.m \ %reldir%/postpad.m \ %reldir%/prepad.m \ - %reldir%/publish.m \ %reldir%/quad2d.m \ %reldir%/quadgk.m \ %reldir%/quadl.m \ @@ -76,7 +63,6 @@ %reldir%/repmat.m \ %reldir%/rot90.m \ %reldir%/rotdim.m \ - %reldir%/saveobj.m \ %reldir%/shift.m \ %reldir%/shiftdim.m \ %reldir%/sortrows.m \ @@ -85,7 +71,6 @@ %reldir%/subsindex.m \ %reldir%/trapz.m \ %reldir%/triplequad.m \ - %reldir%/validateattributes.m \ %reldir%/xor.m %canon_reldir%dir = $(fcnfiledir)/general
--- a/scripts/general/nargchk.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -## Copyright (C) 2008-2017 Bill Denney -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {@var{msgstr} =} nargchk (@var{minargs}, @var{maxargs}, @var{nargs}) -## @deftypefnx {} {@var{msgstr} =} nargchk (@var{minargs}, @var{maxargs}, @var{nargs}, "string") -## @deftypefnx {} {@var{msgstruct} =} nargchk (@var{minargs}, @var{maxargs}, @var{nargs}, "struct") -## Return an appropriate error message string (or structure) if the number of -## inputs requested is invalid. -## -## This is useful for checking to see that the number of input arguments -## supplied to a function is within an acceptable range. -## -## @strong{Caution}: @code{nargchk} is scheduled for deprecation. Use -## @code{narginchk} in all new code. -## @seealso{narginchk, nargoutchk, error, nargin, nargout} -## @end deftypefn - -## Author: Bill Denney <bill@denney.ws> - -function msg = nargchk (minargs, maxargs, nargs, outtype = "string") - - if (nargin < 3 || nargin > 4) - print_usage (); - elseif (minargs > maxargs) - error ("nargchk: MINARGS must be <= MAXARGS"); - elseif (! any (strcmpi (outtype, {"string", "struct"}))) - error ('nargchk: output type must be either "string" or "struct"'); - elseif (! (isscalar (minargs) && isscalar (maxargs) && isscalar (nargs))) - error ("nargchk: MINARGS, MAXARGS, and NARGS must be scalars"); - endif - - msg = struct ("message", "", "identifier", ""); - if (nargs < minargs) - msg.message = "not enough input arguments"; - msg.identifier = "Octave:nargchk:not-enough-inputs"; - elseif (nargs > maxargs) - msg.message = "too many input arguments"; - msg.identifier = "Octave:nargchk:too-many-inputs"; - endif - - if (strcmpi (outtype, "string")) - msg = msg.message; - elseif (isempty (msg.message)) - ## Compatability: Matlab returns a 0x1 empty struct when nargchk passes - msg = resize (msg, 0, 1); - endif - -endfunction - - -## Tests -%!shared stnul, stmin, stmax -%! stnul = resize (struct ("message", "", "identifier", ""), 0, 1); -%! stmin = struct ("message", "not enough input arguments", -%! "identifier", "Octave:nargchk:not-enough-inputs"); -%! stmax = struct ("message", "too many input arguments", -%! "identifier", "Octave:nargchk:too-many-inputs"); -%!assert (nargchk (0, 1, 0), "") -%!assert (nargchk (0, 1, 1), "") -%!assert (nargchk (1, 1, 0), "not enough input arguments") -%!assert (nargchk (0, 1, 2), "too many input arguments") -%!assert (nargchk (0, 1, 2, "string"), "too many input arguments") -## Struct outputs -%!assert (nargchk (0, 1, 0, "struct"), stnul) -%!assert (nargchk (0, 1, 1, "struct"), stnul) -%!assert (nargchk (1, 1, 0, "struct"), stmin) -%!assert (nargchk (0, 1, 2, "struct"), stmax)
--- a/scripts/general/narginchk.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -## Copyright (C) 2012-2017 Carnë Draug -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} narginchk (@var{minargs}, @var{maxargs}) -## Check for correct number of input arguments. -## -## Generate an error message if the number of arguments in the calling function -## is outside the range @var{minargs} and @var{maxargs}. Otherwise, do -## nothing. -## -## Both @var{minargs} and @var{maxargs} must be scalar numeric values. Zero, -## Inf, and negative values are all allowed, and @var{minargs} and -## @var{maxargs} may be equal. -## -## Note that this function evaluates @code{nargin} on the caller. -## -## @seealso{nargoutchk, error, nargout, nargin} -## @end deftypefn - -## Author: Carnë Draug <carandraug+dev@gmail.com> - -function narginchk (minargs, maxargs) - - if (nargin != 2) - print_usage; - elseif (! isnumeric (minargs) || ! isscalar (minargs)) - error ("narginchk: MINARGS must be a numeric scalar"); - elseif (! isnumeric (maxargs) || ! isscalar (maxargs)) - error ("narginchk: MAXARGS must be a numeric scalar"); - elseif (minargs > maxargs) - error ("narginchk: MINARGS cannot be larger than MAXARGS"); - endif - - args = evalin ("caller", "nargin;"); - - if (args < minargs) - error ("narginchk: not enough input arguments"); - elseif (args > maxargs) - error ("narginchk: too many input arguments"); - endif - -endfunction - - -%!function f (nargs, varargin) -%! narginchk (nargs(1), nargs(2)); -%!endfunction - -%!error <too many input arguments> f([0,0]) -%!error <not enough input arguments> f([3, 3], 1) - -%!test -%! f([1,1]); -%!test -%! f([1,5], 2, 3, 4, 5);
--- a/scripts/general/nargoutchk.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ -## Copyright (C) 2008-2017 Bill Denney -## Copyright (C) 2012 Carnë Draug -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} nargoutchk (@var{minargs}, @var{maxargs}) -## @deftypefnx {} {@var{msgstr} =} nargoutchk (@var{minargs}, @var{maxargs}, @var{nargs}) -## @deftypefnx {} {@var{msgstr} =} nargoutchk (@var{minargs}, @var{maxargs}, @var{nargs}, "string") -## @deftypefnx {} {@var{msgstruct} =} nargoutchk (@var{minargs}, @var{maxargs}, @var{nargs}, "struct") -## Check for correct number of output arguments. -## -## In the first form, return an error if the number of arguments is not between -## @var{minargs} and @var{maxargs}. Otherwise, do nothing. Note that this -## function evaluates the value of @code{nargout} on the caller so its value -## must have not been tampered with. -## -## Both @var{minargs} and @var{maxargs} must be numeric scalars. Zero, Inf, -## and negative are all valid, and they can have the same value. -## -## For backwards compatibility, the other forms return an appropriate error -## message string (or structure) if the number of outputs requested is -## invalid. -## -## This is useful for checking to that the number of output arguments supplied -## to a function is within an acceptable range. -## @seealso{narginchk, error, nargout, nargin} -## @end deftypefn - -## Author: Bill Denney <bill@denney.ws> -## Author: Carnë Draug <carandraug+dev@gmail.com> - -function msg = nargoutchk (minargs, maxargs, nargs, outtype) - - ## before matlab's 2011b, nargoutchk would return an error message (just the - ## message in a string). With 2011b, it no longer returns anything, it - ## simply gives an error if the args number is incorrect. - ## To try to keep compatibility with both versions, check nargout and nargin - ## to guess if the caller is expecting a value (old syntax) - ## or none (new syntax). - - if (nargout == 1 && (nargin == 3 || nargin == 4)) - - if (minargs > maxargs) - error ("nargoutchk: MINARGS must be <= MAXARGS"); - elseif (nargin == 3) - outtype = "string"; - elseif (! any (strcmpi (outtype, {"string" "struct"}))) - error ("nargoutchk: output type must be either string or struct"); - elseif (! (isscalar (minargs) && isscalar (maxargs) && isscalar (nargs))) - error ("nargoutchk: MINARGS, MAXARGS, and NARGS must be scalars"); - endif - - msg = struct ("message", "", "identifier", ""); - if (nargs < minargs) - msg.message = "not enough output arguments"; - msg.identifier = "Octave:nargoutchk:not-enough-outputs"; - elseif (nargs > maxargs) - msg.message = "too many output arguments"; - msg.identifier = "Octave:nargoutchk:too-many-outputs"; - endif - - if (strcmpi (outtype, "string")) - msg = msg.message; - elseif (isempty (msg.message)) - ## Compatibility: Matlab returns a 0x1 empty struct when nargoutchk passes - msg = resize (msg, 0, 1); - endif - - elseif (nargout == 0 && nargin == 2) - - if (! isnumeric (minargs) || ! isscalar (minargs)) - error ("nargoutchk: MINARGS must be a numeric scalar"); - elseif (! isnumeric (maxargs) || ! isscalar (maxargs)) - error ("nargoutchk: MAXARGS must be a numeric scalar"); - elseif (minargs > maxargs) - error ("nargoutchk: MINARGS cannot be larger than MAXARGS"); - endif - - args = evalin ("caller", "nargout;"); - - if (args < minargs) - error ("nargoutchk: Not enough output arguments."); - elseif (args > maxargs) - error ("nargoutchk: Too many output arguments."); - endif - - else - print_usage; - endif - -endfunction - - -%!shared stnul, stmin, stmax -%! stnul = resize (struct ("message", "", "identifier", ""), 0, 1); -%! stmin = struct ("message", "not enough output arguments", -%! "identifier", "Octave:nargoutchk:not-enough-outputs"); -%! stmax = struct ("message", "too many output arguments", -%! "identifier", "Octave:nargoutchk:too-many-outputs"); -%!assert (nargoutchk (0, 1, 0), "") -%!assert (nargoutchk (0, 1, 1), "") -%!assert (nargoutchk (1, 1, 0), "not enough output arguments") -%!assert (nargoutchk (0, 1, 2), "too many output arguments") -%!assert (nargoutchk (0, 1, 2, "string"), "too many output arguments") -## Struct outputs -%!assert (nargoutchk (0, 1, 0, "struct"), stnul) -%!assert (nargoutchk (0, 1, 1, "struct"), stnul) -%!assert (nargoutchk (1, 1, 0, "struct"), stmin) -%!assert (nargoutchk (0, 1, 2, "struct"), stmax)
--- a/scripts/general/nthargout.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -## Copyright (C) 2012-2017 Jordi Gutiérrez Hermoso -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} nthargout (@var{n}, @var{func}, @dots{}) -## @deftypefnx {} {} nthargout (@var{n}, @var{ntot}, @var{func}, @dots{}) -## Return the @var{n}th output argument of the function specified by the -## function handle or string @var{func}. -## -## Any additional arguments are passed directly to @var{func}. The total -## number of arguments to call @var{func} with can be passed in @var{ntot}; by -## default @var{ntot} is @var{n}. The input @var{n} can also be a vector of -## indices of the output, in which case the output will be a cell array of the -## requested output arguments. -## -## The intended use @code{nthargout} is to avoid intermediate variables. For -## example, when finding the indices of the maximum entry of a matrix, the -## following two compositions of nthargout -## -## @example -## @group -## @var{m} = magic (5); -## cell2mat (nthargout ([1, 2], @@ind2sub, size (@var{m}), -## nthargout (2, @@max, @var{m}(:)))) -## @result{} 5 3 -## @end group -## @end example -## -## @noindent -## are completely equivalent to the following lines: -## -## @example -## @group -## @var{m} = magic (5); -## [~, idx] = max (@var{M}(:)); -## [i, j] = ind2sub (size (@var{m}), idx); -## [i, j] -## @result{} 5 3 -## @end group -## @end example -## -## It can also be helpful to have all output arguments in a single cell in the -## following manner: -## -## @example -## @var{USV} = nthargout ([1:3], @@svd, hilb (5)); -## @end example -## -## @seealso{nargin, nargout, varargin, varargout, isargout} -## @end deftypefn - -## Author: Jordi Gutiérrez Hermoso - -function out = nthargout (n, varargin) - - if (nargin < 2) - print_usage (); - endif - - if (isa (varargin{1}, "function_handle") || ischar (varargin{1})) - ntot = max (n(:)); - func = varargin{1}; - args = varargin(2:end); - elseif (isnumeric (varargin{1}) - && (isa (varargin{2}, "function_handle") || ischar (varargin{2}))) - ntot = varargin{1}; - func = varargin{2}; - args = varargin(3:end); - else - print_usage (); - endif - - if (any (n != fix (n)) || ntot != fix (ntot) || any (n <= 0) || ntot <= 0) - error ("nthargout: N and NTOT must consist of positive integers"); - endif - - outargs = cell (1, ntot); - - try - [outargs{:}] = feval (func, args{:}); - if (numel (n) > 1) - out = outargs(n); - else - out = outargs{n}; - endif - catch - err = lasterr (); - if (strfind ("some elements undefined in return list", err)) - error ("nthargout: Too many output arguments: %d", ntot); - else - error (err); - endif - end_try_catch - -endfunction - - -%!shared m -%! m = magic (5); -%!assert (nthargout ([1,2], @ind2sub, size (m), nthargout (2, @max, m(:))), {5,3}) -%!assert (nthargout (3, @find, m(m>20)), [23, 24, 25, 21, 22]')
--- a/scripts/general/private/__publish_html_output__.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,430 +0,0 @@ -## Copyright (C) 2016-2017 Kai T. Ohlhus -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {@var{outstr} =} __publish_html_output__ (@var{type}, @var{varargin}) -## -## Internal function. -## -## The first input argument @var{type} defines the required strings -## (@samp{str}) or cell-strings (@samp{cstr}) in @var{varargin} in order -## to produce HTML output. -## -## @var{type} is one of -## -## @itemize @bullet -## @item -## @samp{output_file_extension} () -## -## @item -## @samp{header} (title_str, intro_str, toc_cstr) -## -## @item -## @samp{footer} () -## -## @item -## @samp{code} (str) -## -## @item -## @samp{code_output} (str) -## -## @item -## @samp{section} (str) -## -## @item -## @samp{preformatted_code} (str) -## -## @item -## @samp{preformatted_text} (str) -## -## @item -## @samp{bulleted_list} (cstr) -## -## @item -## @samp{numbered_list} (cstr) -## -## @item -## @samp{graphic} (str) -## -## @item -## @samp{html} (str) -## -## @item -## @samp{latex} (str) -## -## @item -## @samp{text} (str) -## -## @item -## @samp{blockmath} (str) -## -## @item -## @samp{inlinemath} (str) -## -## @item -## @samp{bold} (str) -## -## @item -## @samp{italic} (str) -## -## @item -## @samp{monospaced} (str) -## -## @item -## @samp{link} (url_str, url_str, str) -## -## @item -## @samp{TM} () -## -## @item -## @samp{R} () -## -## @item -## @samp{escape_special_chars} (str) -## @end itemize -## @end deftypefn - -function outstr = __publish_html_output__ (type, varargin) - outstr = feval (["do_" type], varargin{:}); -endfunction - -function outstr = do_output_file_extension () - outstr = ".html"; -endfunction - -function outstr = do_header (title_str, intro_str, toc_cstr) - mathjax_str = sprintf ("%s\n", -'<script type="text/x-mathjax-config">', -"MathJax.Hub.Config({", -" tex2jax: { inlineMath: [['$','$'], ['\\\\(','\\\\)']] },", -" TeX: { equationNumbers: { autoNumber: 'all' } }", -"});", -"</script>", -['<script type="text/javascript" async ', ... - 'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?', ... - 'config=TeX-MML-AM_CHTML"></script>']); - - stylesheet_str = sprintf ("%s\n", -"<style>", -"body > * {", -" max-width: 42em;", -"}", -"body {", -' font-family: "Roboto Condensed", sans-serif;', -" padding-left: 7.5em;", -" padding-right: 7.5em;", -"}", -"pre, code {", -" max-width: 50em;", -" font-family: monospace;", -"}", -"pre.oct-code {", -" border: 1px solid Grey;", -" padding: 5px;", -"}", -"pre.oct-code-output {", -" margin-left: 2em;", -"}", -"span.comment {", -" color: ForestGreen;", -"}",... -"span.keyword {", -" color: Blue;", -"}",... -"span.string {", -" color: DarkOrchid;", -"}",... -"footer {", -" margin-top: 2em;", -" font-size: 80%;", -"}", -"a, a:visited {", -" color: Blue;", -"}", -"h2 {", -' font-family: "Roboto Condensed", serif;', -" margin-top: 1.5em;", -"}", -"h2 a, h2 a:visited {", -" color: Black;", -"}", -"</style>"); - - outstr = sprintf ("%s\n", -"<!DOCTYPE html>", -"<html>", -"<head>", -'<meta charset="UTF-8">', -["<title>" title_str "</title>"], -mathjax_str, -stylesheet_str, -"</head>", -"<body>", -["<h1>" title_str "</h1>"], -intro_str); - - if (! isempty (toc_cstr)) - for i = 1:numel (toc_cstr) - toc_cstr{i} = do_link (["#node" sprintf("%d", i)], toc_cstr{i}); - endfor - outstr = [outstr, "<h2>Contents</h2>", do_bulleted_list(toc_cstr)]; - endif - - ## Reset section counter - do_section (); - -endfunction - -function outstr = do_footer (m_source_str) - outstr = sprintf ("%s\n", -"", -"<footer>", -"<hr>", -['<a href="http://www.octave.org">Published with GNU Octave ' version() '</a>'], -"</footer>", -"<!--", -"##### SOURCE BEGIN #####", -m_source_str, -"##### SOURCE END #####", -"-->", -"</body>", -"</html>"); -endfunction - -function outstr = do_code (str) - outstr = ["\n", '<pre class="oct-code">' syntax_highlight(str) "</pre>\n"]; -endfunction - -function outstr = do_code_output (str) - str = do_escape_special_chars (str); - outstr = ["\n", '<pre class="oct-code-output">' str "</pre>\n"]; -endfunction - -function outstr = do_section (varargin) - persistent counter = 1; - - if (nargin == 0) - outstr = ""; - counter = 1; - return; - endif - - outstr = ['<h2><a id="node' sprintf("%d", counter) '">', ... - varargin{1}, ... - "</a></h2>"]; - - counter++; - -endfunction - -function outstr = do_preformatted_code (str) - outstr = ["\n", '<pre class="pre-code">' syntax_highlight(str) "</pre>\n"]; -endfunction - -function outstr = do_preformatted_text (str) - str = do_escape_special_chars (str); - outstr = ["\n", '<pre class="pre-text">' str "</pre>\n"]; -endfunction - -function outstr = do_bulleted_list (cstr) - outstr = "\n<ul>\n"; - for i = 1:numel (cstr) - outstr = [outstr, "<li>" cstr{i} "</li>\n"]; - endfor - outstr = [outstr, "</ul>\n"]; -endfunction - -function outstr = do_numbered_list (cstr) - outstr = "\n<ol>\n"; - for i = 1:numel (cstr) - outstr = [outstr, "<li>" cstr{i} "</li>\n"]; - endfor - outstr = [outstr, "</ol>\n"]; -endfunction - -function outstr = do_graphic (str) - outstr = ['<img src="' str '" alt="' str '">']; -endfunction - -function outstr = do_html (str) - outstr = ["\n" str "\n"]; -endfunction - -function outstr = do_latex (str) - outstr = ""; -endfunction - -function outstr = do_link (url_str, str) - outstr = ['<a href="' url_str '">' str "</a>"]; -endfunction - -function outstr = do_text (str) - outstr = ["\n<p>" str "</p>\n"]; -endfunction - -function outstr = do_blockmath (str) - outstr = ["$$" str "$$"]; -endfunction - -function outstr = do_inlinemath (str) - outstr = ["$" str "$"]; -endfunction - -function outstr = do_bold (str) - outstr = ["<b>" str "</b>"]; -endfunction - -function outstr = do_italic (str) - outstr = ["<i>" str "</i>"]; -endfunction - -function outstr = do_monospaced (str) - outstr = ["<code>" str "</code>"]; -endfunction - -function outstr = do_TM () - outstr = "™"; -endfunction - -function outstr = do_R () - outstr = "®"; -endfunction - -function str = do_escape_special_chars (str) - str = regexprep (str, '&', '&'); - str = regexprep (str, '<', '<'); - str = regexprep (str, '>', '>'); - ## str = regexprep (str, '"', '"'); ## MATLAB R2017a compatibility. -endfunction - -## SYNTAX_HIGHLIGHT: A primitive parser to highlight syntax via <span> tags. -## FIXME: Needs to be replaced by a better solution. -function outstr = syntax_highlight (str) - str = do_escape_special_chars (str); - outstr = ""; - placeholder_cstr = {}; - i = 1; - plh = 0; - - while (i <= numel (str)) - ## Block comment - if (any (strncmp (str(i:end), {"%{", "#{"}, 2))) - plh_str = ['<span class="comment">', str(i:i+1)]; - i += 2; - while (i <= numel (str) - && ! (any (strncmp (str(i:end), {"%}", "#}"}, 2)))) - plh_str = [plh_str, str(i)]; - i += 1; - endwhile - if (i < numel (str)) - plh_str = [plh_str, str(i:i+1), "</span>"]; - i += 2; - else - plh_str = [plh_str, "</span>"]; - endif - plh += 1; - placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; - ## Line comment - elseif (str(i) == "#" || str(i) == "%") - plh_str = '<span class="comment">'; - idx = find (str(i:end) == "\n", 1); - if (isempty (idx)) - plh_str = [plh_str, str(i:end)]; - i = numel (str) + 1; - else - plh_str = [plh_str, str(i:i+idx-2)]; - i += idx; - endif - plh_str = [plh_str, "</span>\n"]; - plh += 1; - placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; - ## Single quoted string - elseif (str(i) == "'") - plh_str = "<span class=\"string\">'"; - i += 1; - while (i <= numel (str)) - ## Ignore escaped string terminations - if (strncmp (str(i:end), "''", 2)) - plh_str = [plh_str, "''"]; - i += 2; - ## Is char a string termination? - elseif (str(i) == "'") - plh_str = [plh_str, "'"]; - i += 1; - break; - ## Is string terminated by line break? - elseif (str(i) == "\n") - break; - ## String content - else - plh_str = [plh_str, str(i)]; - i += 1; - endif - endwhile - plh_str = [plh_str, "</span>"]; - plh += 1; - placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; - ## Double quoted string - elseif (str(i) == '"') - plh_str = '<span class="string">"'; - i += 1; - while (i <= numel (str)) - ## Is char a string termination? - if (str(i) == '"' && str(i-1) != '\') - plh_str = [plh_str, '"']; - i += 1; - break; - ## Is string terminated by line break? - elseif (str(i) == "\n") - break; - ## String content - else - plh_str = [plh_str, str(i)]; - i += 1; - endif - endwhile - plh_str = [plh_str, "</span>"]; - plh += 1; - placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; - else - outstr = [outstr, str(i)]; - i += 1; - endif - endwhile - - persistent kword_ptn = strjoin (iskeyword (), '|'); - - ## FIXME: remove hack for regexprep once bug #38149 is solved - outstr = [" ", strrep(outstr, "\n", " \n "), " "]; - outstr = regexprep (outstr, - ['(\s)(' kword_ptn ')(\s|\()'], - ['$1<span class="keyword">$2</span>$3']); - ## FIXME: remove hack for regexprep once bug #38149 is solved - outstr = strrep (outstr(2:end-1), " \n ", "\n"); - - ## Restore placeholders - for i = plh:-1:1 - outstr = strrep (outstr, [" PUBLISHPLACEHOLDER", sprintf("%d", i), " "], - placeholder_cstr{i}); - endfor - -endfunction
--- a/scripts/general/private/__publish_latex_output__.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,280 +0,0 @@ -## Copyright (C) 2016-2017 Kai T. Ohlhus -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {@var{outstr} =} __publish_latex_output__ (@var{type}, @var{varargin}) -## -## Internal function. -## -## The first input argument @var{type} defines the required strings -## (@samp{str}) or cell-strings (@samp{cstr}) in @var{varargin} in order -## to produce @LaTeX{} output. -## -## @var{type} is one of -## -## @itemize @bullet -## @item -## @samp{output_file_extension} () -## -## @item -## @samp{header} (title_str, intro_str, toc_cstr) -## -## @item -## @samp{footer} () -## -## @item -## @samp{code} (str) -## -## @item -## @samp{code_output} (str) -## -## @item -## @samp{section} (str) -## -## @item -## @samp{preformatted_code} (str) -## -## @item -## @samp{preformatted_text} (str) -## -## @item -## @samp{bulleted_list} (cstr) -## -## @item -## @samp{numbered_list} (cstr) -## -## @item -## @samp{graphic} (str) -## -## @item -## @samp{html} (str) -## -## @item -## @samp{latex} (str) -## -## @item -## @samp{text} (str) -## -## @item -## @samp{blockmath} (str) -## -## @item -## @samp{inlinemath} (str) -## -## @item -## @samp{bold} (str) -## -## @item -## @samp{italic} (str) -## -## @item -## @samp{monospaced} (str) -## -## @item -## @samp{link} (url_str, url_str, str) -## -## @item -## @samp{TM} () -## -## @item -## @samp{R} () -## -## @item -## @samp{escape_special_chars} (str) -## @end itemize -## @end deftypefn - -function outstr = __publish_latex_output__ (type, varargin) - outstr = feval (["do_" type], varargin{:}); -endfunction - -function outstr = do_output_file_extension () - outstr = ".tex"; -endfunction - -function outstr = do_header (title_str, intro_str, toc_cstr) - publish_comment = sprintf ("%s\n", -"", -"", -"% This document was generated by the publish-function", -["% from GNU Octave " version()], -""); - - latex_preamble = sprintf ("%s\n", -"", -"", -'\documentclass[10pt]{article}', -'\usepackage{listings}', -'\usepackage{mathtools}', -'\usepackage{amssymb}', -'\usepackage{graphicx}', -'\usepackage{hyperref}', -'\usepackage{xcolor}', -'\usepackage{titlesec}', -'\usepackage[utf8]{inputenc}', -'\usepackage[T1]{fontenc}', -'\usepackage{lmodern}'); - - listings_option = sprintf ("%s\n", -"", -"", -'\lstset{', -'language=Octave,', -'numbers=none,', -'frame=single,', -'tabsize=2,', -'showstringspaces=false,', -'breaklines=true}'); - - latex_head = sprintf ("%s\n", -"", -"", -'\titleformat*{\section}{\Huge\bfseries}', -'\titleformat*{\subsection}{\large\bfseries}', -'\renewcommand{\contentsname}{\Large\bfseries Contents}', -'\setlength{\parindent}{0pt}', -"", -'\begin{document}', -"", -['{\Huge\section*{' title_str '}}'], -"", -'\tableofcontents', -'\vspace*{4em}', -""); - - outstr = [publish_comment, latex_preamble, listings_option, latex_head]; - -endfunction - -function outstr = do_footer (m_source_str) - outstr = ["\n\n" '\end{document}' "\n"]; -endfunction - -function outstr = do_code (str) - outstr = ['\begin{lstlisting}' "\n", str, "\n" '\end{lstlisting}' "\n"]; -endfunction - -function outstr = do_code_output (str) - outstr = sprintf ("%s\n", -'\begin{lstlisting}[language={},xleftmargin=5pt,frame=none]', -str, -'\end{lstlisting}'); -endfunction - -function outstr = do_section (str) - outstr = sprintf ("%s\n", -"", -"", -'\phantomsection', -['\addcontentsline{toc}{section}{' str '}'], -['\subsection*{' str '}'], -""); -endfunction - -function outstr = do_preformatted_code (str) - outstr = sprintf ("%s\n", -'\begin{lstlisting}', -str, -'\end{lstlisting}'); -endfunction - -function outstr = do_preformatted_text (str) - outstr = sprintf ("%s\n", -'\begin{lstlisting}[language={}]', -str, -'\end{lstlisting}'); -endfunction - -function outstr = do_bulleted_list (cstr) - outstr = ["\n" '\begin{itemize}' "\n"]; - for i = 1:numel (cstr) - outstr = [outstr, '\item ' cstr{i} "\n"]; - endfor - outstr = [outstr, '\end{itemize}' "\n"]; -endfunction - -function outstr = do_numbered_list (cstr) - outstr = ["\n" '\begin{enumerate}' "\n"]; - for i = 1:numel (cstr) - outstr = [outstr, '\item ' cstr{i} "\n"]; - endfor - outstr = [outstr, "\\end{enumerate}\n"]; -endfunction - -function outstr = do_graphic (str) - outstr = sprintf ("%s\n", -'\begin{figure}[!ht]', -['\includegraphics[width=\textwidth]{' str '}'], -'\end{figure}'); -endfunction - -function outstr = do_html (str) - outstr = ""; -endfunction - -function outstr = do_latex (str) - outstr = ["\n" str "\n"]; -endfunction - -function outstr = do_link (url_str, str) - outstr = ['\href{' url_str '}{' str '}']; -endfunction - -function outstr = do_text (str) - outstr = ["\n\n" str "\n\n"]; -endfunction - -function outstr = do_blockmath (str) - outstr = ["$$" str "$$"]; -endfunction - -function outstr = do_inlinemath (str) - outstr = ["$" str "$"]; -endfunction - -function outstr = do_bold (str) - outstr = ['\textbf{' str '}']; -endfunction - -function outstr = do_italic (str) - outstr = ['\textit{' str '}']; -endfunction - -function outstr = do_monospaced (str) - outstr = ['\texttt{' str '}']; -endfunction - -function outstr = do_TM () - outstr = '\texttrademark '; -endfunction - -function outstr = do_R () - outstr = '\textregistered '; -endfunction - -function str = do_escape_special_chars (str) - ## Escape \, {, }, &, %, #, _, ~, ^, <, > - str = regexprep (str, '\\', "\\ensuremath{\\backslash}"); - str = regexprep (str, '(?<!\\)(\{|\}|&|%|#|_)', '\\$1'); - ## Revert accidential {} replacements for backslashes - str = strrep (str, '\ensuremath\{\backslash\}', '\ensuremath{\backslash}'); - str = regexprep (str, '(?<!\\)~', "\\ensuremath{\\tilde{\\;}}"); - str = regexprep (str, '(?<!\\)\^', "\\^{}"); - str = regexprep (str, '(?<!\\)<', "\\ensuremath{<}"); - str = regexprep (str, '(?<!\\)>', "\\ensuremath{>}"); -endfunction
--- a/scripts/general/publish.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1071 +0,0 @@ -## Copyright (C) 2016-2017 Kai T. Ohlhus <k.ohlhus@gmail.com> -## Copyright (C) 2010 Fotios Kasolis <fotios.kasolis@gmail.com> -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} publish (@var{file}) -## @deftypefnx {} {} publish (@var{file}, @var{output_format}) -## @deftypefnx {} {} publish (@var{file}, @var{option1}, @var{value1}, @dots{}) -## @deftypefnx {} {} publish (@var{file}, @var{options}) -## @deftypefnx {} {@var{output_file} =} publish (@var{file}, @dots{}) -## -## Generate a report from the Octave script file @var{file} in one of several -## output formats. -## -## The generated reports interpret any Publishing Markup in comments, which is -## explained in detail in the GNU Octave manual. Assume the following example, -## using some Publishing Markup, to be the contents of the script file -## @file{pub_example.m}: -## -## @example -## @group -## ## Headline title -## # -## # Some *bold*, _italic_, or |monospaced| Text with -## # a <http://www.octave.org link to *GNU Octave*>. -## ## -## -## # "Real" Octave commands to be evaluated -## sombrero () -## -## %% @sc{matlab} comment style ('%') is supported as well -## % -## % * Bulleted list item 1 -## % * Bulleted list item 2 -## % -## % # Numbered list item 1 -## % # Numbered list item 2 -## @end group -## @end example -## -## To publish this script file, type @code{publish ("pub_example.m")}. -## -## With only @var{file} given, a HTML report is generated in a subdirectory -## @file{html} relative to the current working directory. The Octave commands -## are evaluated in a separate context and any figures created while executing -## the script file are included in the report. All formatting syntax of -## @var{file} is treated according to the specified output format and included -## in the report. -## -## Using @code{publish (@var{file}, @var{output_format})} is equivalent to the -## function call using a structure -## -## @example -## @group -## @var{options}.format = @var{output_format}; -## publish (@var{file}, @var{options}) -## @end group -## @end example -## -## @noindent -## which is described below. The same holds for using option/value pairs -## -## @example -## @group -## @var{options}.@var{option1} = @var{value1}; -## publish (@var{file}, @var{options}) -## @end group -## @end example -## -## The structure @var{options} can have the following field names. If a field -## name is not specified, the default value is used: -## -## @itemize @bullet -## @item -## @samp{format} --- Output format of the published script file, one of -## -## @samp{html} (default), @samp{doc}, @samp{latex}, @samp{ppt}, -## @samp{pdf}, or @samp{xml}. -## -## The output formats @samp{doc}, @samp{ppt}, and @samp{xml} are not currently -## supported. To generate a @samp{doc} report, open a generated @samp{html} -## report with your office suite. -## -## In Octave custom formats are supported by implementing all callback -## subfunctions in a function file named -## @samp{__publish_<custom format>_output__.m}. To obtain a template for the -## HTML format type: -## -## @example -## @group -## edit (fullfile (fileparts (which ("publish")), ... -## "private", "__publish_html_output__.m")) -## @end group -## @end example -## -## @item -## @samp{outputDir} --- Full path of the directory where the generated report -## will be located. If no directory is given, the report is generated in a -## subdirectory @file{html} relative to the current working directory. -## -## @item -## @samp{stylesheet} --- Not supported, only for @sc{matlab} compatibility. -## -## @item -## @samp{createThumbnail} --- Not supported, only for @sc{matlab} -## compatibility. -## -## @item -## @samp{figureSnapMethod} --- Not supported, only for @sc{matlab} -## compatibility. -## -## @item -## @samp{imageFormat} --- Desired format for any images produced while -## evaluating the code. The allowed image formats depend on the output format: -## -## @itemize @bullet -## @item @samp{html}, @samp{xml} --- @samp{png} (default), any image format -## supported by Octave -## -## @item @samp{latex} --- @samp{epsc2} (default), any image format supported by -## Octave -## -## @item @samp{pdf} --- @samp{jpg} (default) or @samp{bmp}, note @sc{matlab} -## uses @samp{bmp} as default -## -## @item @samp{doc} or @samp{ppt} --- @samp{png} (default), @samp{jpg}, -## @samp{bmp}, or @samp{tiff} -## @end itemize -## -## @item -## @samp{maxWidth} and @samp{maxHeight} --- Maximum width (height) of the -## produced images in pixels. An empty value means no restriction. Both -## values must be set in order for the option to work properly. -## -## @samp{[]} (default), integer value @geq{} 0 -## -## @item -## @samp{useNewFigure} --- Use a new figure window for figures created by the -## evaluated code. This avoids side effects with already opened figure -## windows. -## -## @samp{true} (default) or @samp{false} -## -## @item -## @samp{evalCode} --- Evaluate code of the Octave source file -## -## @samp{true} (default) or @samp{false} -## -## @item -## @samp{catchError} --- Catch errors while evaluating code and continue -## -## @samp{true} (default) or @samp{false} -## -## @item -## @samp{codeToEvaluate} --- Octave commands that should be evaluated prior to -## publishing the script file. These Octave commands do not appear in the -## generated report. -## -## @item -## @samp{maxOutputLines} --- Maximum number of output lines from code -## evaluation which are included in output. -## -## @samp{Inf} (default) or integer value > 0 -## -## @item -## @samp{showCode} --- Show the evaluated Octave commands in the generated -## report -## -## @samp{true} (default) or @samp{false} -## @end itemize -## -## The option output @var{output_file} is a string with path and file name -## of the generated report. -## -## @seealso{grabcode} -## @end deftypefn - -function output_file = publish (file, varargin) - - if (nargin < 1) - print_usage (); - endif - - if (exist (file, "file") != 2) - error ("publish: FILE does not exist"); - endif - - ## Check file to be in Octave's load path - [file_path, file_name, file_ext] = fileparts (file); - if (isempty (file_path)) - file_path = pwd; - endif - if (exist ([file_name, file_ext]) != 2) - error (["publish: " file " is not in the load path"]); - endif - - ## Check file extension and that file is an Octave script - file_info = __which__ (file_name); - if (! strcmp (file_ext, ".m") || ! strcmp (file_info.type, "script")) - error ("publish: only script files can be published"); - endif - - ## Check file to be parsable - __parse_file__ (file); - - ## Get structure with necessary options - options = struct (); - if (numel (varargin) == 1) - ## Call: publish (file, format) - if (ischar (varargin{1})) - options.format = varargin{1}; - ## Call: publish (file, options) - elseif (isstruct (varargin{1})) - options = varargin{1}; - else - error ("publish: second argument must be OUTPUT_FORMAT or OPTIONS"); - endif - ## Call: publish (file, Name1, Value1, Name2, Value2, ...) - elseif (rem (numel (varargin), 2) == 0 - && all (cellfun (@ischar, varargin(1:2:end)))) - options = cell2struct (varargin(2:2:end), varargin(1:2:end), 2); - else - error ("publish: invalid arguments"); - endif - - ## Validate options struct - - ## Options for the output - if (! isfield (options, "format")) - options.format = "html"; - else - ## FIXME: Implement remaining formats - if (any (strcmpi (options.format, {"doc", "ppt", "xml"}))) - error ('publish: Output format "%s" is not yet supported', - options.format); - endif - ## Supported or custom output format - supported_formats = {"html", "doc", "latex", "ppt", "xml", "pdf"}; - if (! any (strcmpi (options.format, supported_formats))) - ## Check existence of custom formatter - custom_formatter = ["__publish_", options.format, "_output__"]; - if (! exist (custom_formatter, "file")) - error (['publish: Custom output format "%s" requires the ', ... - "formatter function:\n\n\t%s\n\n\t", ... - 'See "help publish" for more information.'], - options.format, custom_formatter); - endif - else - options.format = validatestring (options.format, supported_formats); - endif - endif - - if (! isfield (options, "outputDir")) - ## Matlab R2016a doc says default is "", but specifies to create a - ## subdirectory named "html" in the current working directory. - options.outputDir = fullfile (file_path, "html"); - elseif (! ischar (options.outputDir)) - error ("publish: OUTPUTDIR must be a string"); - endif - - if (! isfield (options, "stylesheet")) - options.stylesheet = ""; - elseif (! ischar (options.stylesheet)) - error ("publish: STYLESHEET must be a string"); - endif - - ## Options for the figures - if (! isfield (options, "createThumbnail")) - options.createThumbnail = true; - elseif (! isscalar (options.createThumbnail) - || ! isreal (options.createThumbnail)) - error ("publish: CREATETHUMBNAIL must be TRUE or FALSE"); - endif - - if (! isfield (options, "figureSnapMethod")) - options.figureSnapMethod = "entireGUIWindow"; - else - options.figureSnapMethod = validatestring (options.figureSnapMethod, ... - {"entireGUIWindow", "print", "getframe", "entireFigureWindow"}); - ## FIXME: implement other SnapMethods - warning ("publish: option FIGURESNAPMETHOD currently not supported"); - endif - - if (! isfield (options, "imageFormat")) - switch (options.format) - case "latex" - options.imageFormat = "epsc2"; - case "pdf" - ## Note: Matlab R2016a uses bmp as default - options.imageFormat = "jpg"; - otherwise - options.imageFormat = "png"; - endswitch - elseif (! ischar (options.imageFormat)) - error ("publish: IMAGEFORMAT must be a string"); - else - ## Check valid imageFormat for chosen format - ## html, latex, and xml accept any imageFormat - switch (options.format) - case {"doc", "ppt"} - options.imageFormat = validatestring (options.imageFormat, - {"png", "jpg", "bmp", "tiff"}); - case "pdf" - options.imageFormat = validatestring (options.imageFormat, - {"bmp", "jpg"}); - endswitch - endif - - if (! isfield (options, "maxHeight")) - options.maxHeight = []; - elseif (! isscalar (options.maxHeight) || options.maxHeight < 1) - error ("publish: MAXHEIGHT must be a positive integer"); - else - options.maxHeight = uint64 (options.maxHeight); - endif - - if (! isfield (options, "maxWidth")) - options.maxWidth = []; - elseif (! isscalar (options.maxWidth) || options.maxWidth < 1) - error ("publish: MAXWIDTH must be a positive integer"); - else - options.maxWidth = uint64 (options.maxWidth); - endif - - if (! isfield (options, "useNewFigure")) - options.useNewFigure = true; - elseif (! isscalar (options.useNewFigure) || ! isreal (options.useNewFigure)) - error ("publish: USENEWFIGURE must be TRUE or FALSE"); - endif - - ## Options for the code - if (! isfield (options, "evalCode")) - options.evalCode = true; - elseif (! isscalar (options.evalCode) || ! isreal (options.evalCode)) - error ("publish: EVALCODE must be TRUE or FALSE"); - endif - - if (! isfield (options, "catchError")) - options.catchError = true; - elseif (! isscalar (options.catchError) || ! isreal (options.catchError)) - error ("publish: CATCHERROR must be TRUE or FALSE"); - endif - - if (! isfield (options, "codeToEvaluate")) - options.codeToEvaluate = ""; - elseif (! ischar (options.codeToEvaluate)) - error ("publish: CODETOEVALUTE must be a string"); - endif - - if (! isfield (options, "maxOutputLines")) - options.maxOutputLines = Inf; - elseif (! isscalar (options.maxOutputLines) || options.maxOutputLines < 0) - error ("publish: MAXOUTPUTLINES must be an integer >= 0"); - else - options.maxOutputLines = uint64 (options.maxOutputLines); - endif - - if (! isfield (options, "showCode")) - options.showCode = true; - elseif (! isscalar (options.showCode) || ! isreal (options.showCode)) - error ("publish: SHOWCODE must be TRUE or FALSE"); - endif - - doc.title = ""; - doc.intro = ""; - doc.body = cell (); - doc.m_source = deblank (read_file_to_cellstr (file)); - doc.m_source_file_name = file; - - ## Split code and paragraphs, find formatting - doc = parse_m_source (doc); - - ## Create output directory - [status, msg] = mkdir (options.outputDir); - if (status != 1) - error ("publish: cannot create output directory: %s", msg); - endif - - if (options.evalCode) - doc = eval_code (doc, options); - endif - - output_file = create_output (doc, options); - -endfunction - - -function doc = parse_m_source (doc) - ## PARSE_M_SOURCE First parsing level - ## This function extracts the overall structure (paragraphs and code - ## sections) given in doc.m_source. - ## - ## The result is written to doc.body, which then contains a cell - ## vector of structs, either of - ## - ## a) {struct ("type", "code", ... - ## "lines", [a, b], ... - ## "output", [])} - ## b) {struct ("type", "section", ... - ## "content", title_str)} - ## - ## Second parsing level is invoked for the paragraph contents, resulting - ## in more elements for doc.body. - - if (isempty (doc.m_source)) - return; # Nothing to parse - endif - - ## Parsing helper functions - ## - ## Checks line to have N "%" or "#" lines - ## followed either by a space or end of string - is_publish_markup = @(cstr, N) ... - any (strncmp (char (cstr), {"%%%", "##"}, N)) ... - && ((length (char (cstr)) == N) || ((char (cstr))(N + 1) == " ")); - ## Checks line of cellstring to be a paragraph line - is_paragraph = @(cstr) is_publish_markup (cstr, 1); - ## Checks line of cellstring to be a section headline - is_head = @(cstr) is_publish_markup (cstr, 2); - ## Checks line of cellstring to be a headline without section break, using - ## the cell mode in Matlab (for compatibility), just treated as a new head. - is_no_break_head = @(cstr) is_publish_markup (cstr, 3); - - ## Find the indices of paragraphs starting with "%%", "##", or "%%%" - par_start_idx = find (cellfun (is_head, doc.m_source) - | cellfun (is_no_break_head, doc.m_source)); - - ## If the whole document is code - if (isempty (par_start_idx)) - doc.body{end+1}.type = "code"; - doc.body{end}.content = strtrim (strjoin (doc.m_source, "\n")); - doc.body{end}.lines = [1, length(doc.m_source)]; - doc.body{end}.output = {}; - return; - endif - - ## Determine continuous range of paragraphs - par_end_idx = [par_start_idx(2:end) - 1, length(doc.m_source)]; - for i = 1:numel (par_end_idx) - idx = find (! cellfun (is_paragraph, - doc.m_source(par_start_idx(i) + 1:par_end_idx(i)))); - if (! isempty (idx)) - par_end_idx(i) = par_start_idx(i) + idx(1) - 1; - endif - endfor - ## Code sections between paragraphs - code_start_idx = par_end_idx(1:end-1) + 1; - code_end_idx = par_start_idx(2:end) - 1; - ## Code at the beginning? - if (par_start_idx(1) > 1) - code_start_idx = [1, code_start_idx]; - code_end_idx = [par_start_idx(1) - 1, code_end_idx]; - endif - ## Code at the end? - if (par_end_idx(end) < length (doc.m_source)) - code_start_idx = [code_start_idx, par_end_idx(end) + 1]; - code_end_idx = [code_end_idx, length(doc.m_source)]; - endif - ## Remove overlaps - idx = code_start_idx > code_end_idx; - code_start_idx(idx) = []; - code_end_idx(idx) = []; - ## Remove empty code blocks - idx = []; - for i = 1:numel (code_start_idx) - if (all (cellfun (@(cstr) isempty (char (cstr)), - doc.m_source(code_start_idx(i):code_end_idx(i))))) - idx = [idx, i]; - endif - endfor - code_start_idx(idx) = []; - code_end_idx(idx) = []; - - ## Try to find a document title and introduction text - ## 1. First paragraph must start in first line - ## 2. Second paragraph must start before any code - title_offset = 0; - if (is_head (doc.m_source{1}) - && ! isempty (par_start_idx) - && par_start_idx(1) == 1 - && (isempty (code_start_idx) - || (length (par_start_idx) > 1 - && par_start_idx(2) < code_start_idx(1)))) - doc.title = doc.m_source{1}; - doc.title = doc.title(4:end); - content = doc.m_source(2:par_end_idx(1)); - ## Strip leading "# " - content = cellfun (@(c) cellstr (c(3:end)), content); - doc.intro = parse_paragraph_content (content); - title_offset = 1; - endif - - ## Add non-empty paragraphs and code to doc - j = 1; - i = (1 + title_offset); - while (i <= numel (par_start_idx) || j <= numel (code_start_idx)) - ## Add code while there is code left - ## and code is before the next paragraph or there are no more paragraphs - while (j <= numel (code_start_idx) - && (i > numel (par_start_idx) - || par_start_idx(i) > code_start_idx(j))) - doc.body{end+1}.type = "code"; - lines = [code_start_idx(j), code_end_idx(j)]; - doc.body{end}.content = ... - strtrim (strjoin (doc.m_source(lines(1):lines(2)), "\n")); - doc.body{end}.lines = lines; - doc.body{end}.output = {}; - j++; - endwhile - - if (i <= numel (par_start_idx)) - type_str = "section"; - title_str = doc.m_source{par_start_idx(i)}; - if (is_head (doc.m_source(par_start_idx(i)))) - title_str = title_str(4:end); - else - title_str = title_str(5:end); - endif - ## Append, if paragraph title is given - if (! isempty (title_str)) - doc.body{end+1}.type = type_str; - doc.body{end}.content = title_str; - endif - - content = doc.m_source(par_start_idx(i) + 1:par_end_idx(i)); - ## Strip leading "# " - content = cellfun (@(c) cellstr (c(3:end)), content); - doc.body = [doc.body, parse_paragraph_content(content)]; - i++; - endif - endwhile - -endfunction - - -function p_content = parse_paragraph_content (content) - ## PARSE_PARAGRAPH_CONTENT second parsing level - ## - ## Parses the content of a paragraph (without potential title) and - ## returns a cell vector of structs, that can be appended to doc.body, - ## either of - ## - ## a) {struct ("type", "preformatted_code", ... - ## "content", code_str)} - ## b) {struct ("type", "preformatted_text", ... - ## "content", text_str)} - ## c) {struct ("type", "bulleted_list", ... - ## "content", {"item1", "item2", ..})} - ## d) {struct ("type", "numbered_list", ... - ## "content", {"item1", "item2", ..})} - ## e) {struct ("type", "include", ... - ## "content", file_str)} - ## f) {struct ("type", "graphic", ... - ## "content", file_str)} - ## g) {struct ("type", "html", ... - ## "content", html_str)} - ## h) {struct ("type", "latex", ... - ## "content", latex_str)} - ## i) {struct ("type", "text", ... - ## "content", text_str)} - ## - ## Option i) might contain: - ## - ## * Italic "_", bold "*", and monospaced "|" text - ## * Inline "$" and block "$$" LaTeX math - ## * Links - ## * Trademark symbols - - p_content = cell (); - - if (isempty (content)) - return; - endif - - ## Extract <html> and <latex> blocks recursively. - content_str = strjoin (content, "\n"); - tags = {"html", "latex"}; - for i = 1:length(tags) - tok = regexp (content_str, ... - ['(.*?)(^|\n\n)(<', tags{i}, '>)\n(.*?)\n(<\/', ... - tags{i}, '>)($|\n\n)(.*)'], "tokens", "once"); - if (! isempty (tok)) - ## If there was some text before that block --> recursion - if (! strcmpi (tok{1}, ["<", tags{i}, ">"])) - p_content = parse_paragraph_content (strsplit (tok{1}, "\n")); - tok(1:2) = []; - endif - ## Extract the block content - p_content{end+1}.type = tags{i}; - p_content{end}.content = tok{2}; - ## If there was some text after that block --> recursion - if (length (tok) == 5) - p_content = [p_content, ... - parse_paragraph_content(strsplit (tok{5}, "\n"))]; - endif - return; - endif - endfor - - ## Split into blocks separated by empty lines - idx = [0, find(cellfun (@isempty, content)), length(content) + 1]; - - ## For each block - for i = find (diff (idx) > 1) - block = content(idx(i) + 1:idx(i+1) - 1); - - ## Octave code (two leading spaces) - if (all (cellfun (@(c) strncmp (char (c), " ", 2), block))) - p_content{end+1}.type = "preformatted_code"; - block = cellfun (@(c) cellstr (c(3:end)), block); - p_content{end}.content = strjoin (block, "\n"); - continue; - endif - - ## Preformatted text (one leading space) - if (all (cellfun (@(c) strncmp (char (c), " ", 1), block))) - p_content{end+1}.type = "preformatted_text"; - block = cellfun (@(c) cellstr (c(2:end)), block); - p_content{end}.content = strjoin (block, "\n"); - continue; - endif - - ## Bulleted list starts with "* " - if (strncmp (block{1}, "* ", 2)) - p_content{end+1}.type = "bulleted_list"; - tmpstr = strjoin (block, "\n"); - ## Remove first "* " - tmpstr = tmpstr(3:end); - ## Split items - p_content{end}.content = strsplit (tmpstr, "\n* "); - continue; - endif - - ## Numbered list starts with "# " - if (strncmp (block{1}, "# ", 2)) - p_content{end+1}.type = "numbered_list"; - tmpstr = strjoin (block, "\n"); - ## Remove first "# " - tmpstr = tmpstr(3:end); - ## Split items - p_content{end}.content = strsplit (tmpstr, "\n# "); - continue; - endif - - ## Include <include>fname.m</include> - if (! isempty (fname = regexpi (strjoin (block, ""), - '^<include>(.*)</include>$', - "tokens"))) - ## Includes result in preformatted code - p_content{end+1}.type = "preformatted_code"; - include_code = read_file_to_cellstr (strtrim ((fname{1}){1})); - p_content{end}.content = strjoin (include_code, "\n"); - - continue; - endif - - ## Graphic <<myGraphic.png>> - if (! isempty (fname = regexpi (strjoin (block, ""), - '^<<(.*)>>$', - "tokens"))) - p_content{end+1}.type = "graphic"; - p_content{end}.content = strtrim ((fname{1}){1}); - continue; - endif - - ## Now it can be only normal text or markups belonging to normal text - ## that are handled while output generation: - ## - ## * Italic "_", bold "*", and monospaced "|" text - ## * Inline "$" and block "$$" LaTeX math - ## * Links - ## * Trademark symbols - p_content{end+1}.type = "text"; - p_content{end}.content = strjoin (block, "\n"); - endfor -endfunction - - -function m_source = read_file_to_cellstr (file) - ## READ_FILE_TO_CELLSTR reads a given file line by line into a cellstring - - fid = fopen (file, "r"); - i = 0; - do - m_source{++i} = fgetl (fid); - until (! ischar (m_source{i})) - fclose (fid); - m_source = m_source(1:end-1); # No EOL -endfunction - - -function ofile = create_output (doc, options) - ## CREATE_OUTPUT creates the desired output file - - formatter = []; - switch (options.format) - case "html" - formatter = @__publish_html_output__; - case {"latex", "pdf"} - formatter = @__publish_latex_output__; - otherwise - ## Custom formatter - formatter = eval (["@__publish_", options.format, "_output__"]); - endswitch - - ## Use title, or if not given, the m-file name - title_str = doc.title; - if (isempty (title_str)) - [~, title_str] = fileparts (doc.m_source_file_name); - endif - - content = formatter ("header", - formatter ("escape_special_chars", title_str), - format_output (doc.intro, formatter, options), - get_toc (doc.body, formatter)); - content = [content, format_output(doc.body, formatter, options)]; - content = [content, formatter("footer", strjoin (doc.m_source, "\n"))]; - - ## Write file - [~, ofile] = fileparts (doc.m_source_file_name); - ofile_name = [ofile, formatter("output_file_extension")]; - ofile = fullfile (options.outputDir, ofile_name); - fid = fopen (ofile, "w"); - fputs (fid, content); - fclose (fid); - - ## Compile LaTeX, if compiler found - if (strcmp (options.format, "pdf")) - status = system ("pdflatex --version"); - if (status == 0) - for i = 1:2 - ## FIXME: This looks very likely to break when switching OS - system (["cd ", options.outputDir," && pdflatex ", ofile_name]); - endfor - endif - endif -endfunction - - -function toc_cstr = get_toc (cstr, formatter) - ## GET_TOC extracts the table of contents from a cellstring (e.g., doc.body) - ## with each section headline as a cell in a returned cellstring. - - toc_cstr = cell (); - for i = 1:numel (cstr) - if (strcmp (cstr{i}.type, "section")) - toc_cstr{end+1} = format_text (cstr{i}.content, formatter); - endif - endfor -endfunction - - -function str = format_output (cstr, formatter, options) - ## FORMAT_OUTPUT steps through all blocks (doc.intro or doc.body) in cstr and - ## produces a single result string with the source code in the desired output - ## format. - ## - ## formatter has the only knowledge how to enforce the target format - ## and produces for each block the necessary target format source string. - - str = ""; - for i = 1:numel (cstr) - switch (cstr{i}.type) - case "code" - if (options.showCode) - str = [str, formatter("code", cstr{i}.content)]; - endif - if ((options.evalCode) && (! isempty (cstr{i}.output))) - str = [str, formatter("code_output", cstr{i}.output)]; - endif - case {"text", "section"} - str = [str, formatter(cstr{i}.type, ... - format_text (cstr{i}.content, formatter))]; - case {"bulleted_list", "numbered_list"} - items = cellfun (@(str) format_text(str, formatter), ... - cstr{i}.content, "UniformOutput", false); - str = [str, formatter(cstr{i}.type, items)]; - otherwise - str = [str, formatter(cstr{i}.type, cstr{i}.content)]; - endswitch - endfor - -endfunction - - -function str = format_text (str, formatter) - ## FORMAT_TEXT formats inline formats in strings. - ## These are: links, block/inline math, bold, italic, monospaced, (TM), (R) - - ## Helper to clarify the following regular expressions. It is suitable for - ## inline formatting, that is delimited literally at start and end by - ## `delim`. `term` is an indicating character for the end delimiter. - ## - ## Best explained by example ('^' start and '$' end of input): - ## - ## Positive matches: - ## - ## ^*bold*$ - ## ^*bold*.$ - ## ^(*bold*)$ - ## ^ *bold* $ - ## ^Text *bold* text$ - ## ^*bold text*$ - ## - ## Negative matches: - ## - ## ^Text*bold*text$ - ## ^*bold *$ - ## ^* bold* $ - ## ^*bold text *$ - ## - regex_helper = @(delim, term) ['(^|(?<=\s)|(?=\W))', delim, ... - '(?!\s)[^', term, ']*(?<!\s)', delim, '($|(?=\s)|(?=\W))']; - - ## Regular expressions for the formats: - ## - ## 1) Links "<http://www.someurl.com>" - ## 2) Links "<octave:Function TEXT>" - ## 3) Links "<http://www.someurl.com TEXT>" - ## 4) LaTeX block math "$$x^2$$" - ## 5) LaTeX inline math "$x^2$" - ## 6) Bold *text* - ## 7) Italic _text_ - ## 8) Monospaced |text| - ## 9) (TM) or (R) - regexes = {'<\S{3,}[^\s<>]*>', ... - '<octave:[^\s<>]* *[^<>$]*>', ... - '<\S{3,}[^\s<>]* *[^<>$]*>', ... - regex_helper('\$\$', '$'), ... - regex_helper('\$', '$'), ... - regex_helper('\*', '*'), ... - regex_helper('_', '_'), ... - regex_helper('\|', '|'), ... - '\((TM|R)\)'}; - - ## Function to escape some special characters for the GNU Octave manual, - ## see https://www.gnu.org/software/texinfo/manual/texinfo/html_node/HTML-Xref-Node-Name-Expansion.html - texinfo_esc = @(str) strrep (strrep (str, "-", "_002d"), "_", "_005f"); - - ## Substitute all occurrences with placeholders - placeholder_cstr = {}; - plh = 0; - for i = 1:numel (regexes) - cstr = regexp (str, regexes{i}, "match"); - for j = 1:numel (cstr) - plh += 1; - str = regexprep (str, regexes{i}, ["PUBLISHPLACEHOLDER" num2str(plh)], - "once"); - switch (i) - case 1 - ## Links "<http://www.someurl.com>" - url = cstr{j}; - cstr{j} = formatter ("link", url(2:end-1), url(2:end-1)); - case 2 - ## Links "<octave:Function TEXT>" - idx = strfind (cstr{j}, " "); - url = cstr{j}; - url = texinfo_esc (url(9:idx-1)); - v = version (); - if (v(end) == '+') - v = "interpreter"; - endif - url = sprintf ( ... - "https://www.gnu.org/software/octave/doc/%s/XREF%s.html", v, url); - txt = cstr{j}; - txt = format_text (txt(idx+1:end-1), formatter); - cstr{j} = formatter ("link", url, txt); - case 3 - ## Links "<http://www.someurl.com TEXT>" - idx = strfind (cstr{j}, " "); - url = cstr{j}; - url = url(2:idx-1); - txt = cstr{j}; - txt = format_text (txt(idx+1:end-1), formatter); - cstr{j} = formatter ("link", url, txt); - case 4 - ## LaTeX block math "$$" - txt = cstr{j}; - cstr{j} = formatter ("blockmath", txt(3:end-2)); - case 5 - ## LaTeX inline math "$" - txt = cstr{j}; - cstr{j} = formatter ("inlinemath", txt(2:end-1)); - case 6 - ## Bold - txt = cstr{j}; - cstr{j} = formatter ("bold", format_text (txt(2:end-1), formatter)); - case 7 - ## Italic - txt = cstr{j}; - cstr{j} = formatter ("italic", format_text (txt(2:end-1), formatter)); - case 8 - ## Monospaced - txt = cstr{j}; - cstr{j} = formatter ("monospaced", format_text (txt(2:end-1), ... - formatter)); - case 9 - ## (TM) or (R) - txt = cstr{j}; - cstr{j} = formatter (txt(2:end-1)); - endswitch - endfor - placeholder_cstr = [placeholder_cstr, cstr]; - endfor - - ## Replace special symbols - str = formatter ("escape_special_chars", str); - - ## Restore placeholders - for i = plh:-1:1 - str = strrep (str, ["PUBLISHPLACEHOLDER" sprintf("%d", i)], - placeholder_cstr{i}); - endfor - -endfunction - - -function doc = eval_code (doc, options) - ## EVAL_CODE Third level parsing - ## - ## Generates the output of the script code and takes care of generated - ## figures. - - ## Necessary as the code does not run interactively - page_screen_output (false, "local"); - - ## Remember previously opened figures - fig_ids = findall (0, "type", "figure"); - [~, fig_name] = fileparts (doc.m_source_file_name); - fig_num = 1; - fig_list = struct (); - - ## File used as temporary context - tmp_context = [tempname() ".var"]; - - ## Evaluate code, that does not appear in the output. - eval_code_helper (tmp_context, options.codeToEvaluate); - - ## Create a new figure, if there are existing plots - if (! isempty (fig_ids) && options.useNewFigure) - figure (); - endif - - for i = 1:numel (doc.body) - if (strcmp (doc.body{i}.type, "code")) - r = doc.body{i}.lines; - code_str = strjoin (doc.m_source(r(1):r(2)), "\n"); - if (options.catchError) - try - doc.body{i}.output = eval_code_helper (tmp_context, code_str); - catch err - doc.body{i}.output = cellstr (["error: ", err.message, ... - "\n\tin:\n\n", code_str]); - end_try_catch - else - doc.body{i}.output = eval_code_helper (tmp_context, code_str); - endif - - ## Check for newly created figures ... - fig_ids_new = setdiff (findall (0, "type", "figure"), fig_ids); - ## ... and save them - for j = 1:numel (fig_ids_new) - drawnow (); - if (isempty (get (fig_ids_new(j), "children"))) - continue; - endif - fname = [fig_name, "-", sprintf("%d", fig_num)]; - if (strncmp (options.imageFormat, "eps", 3)) - fname = [fname ".eps"]; - else - fname = [fname "." options.imageFormat]; - endif - print_opts = {fig_ids_new(j), ... - fullfile(options.outputDir, fname), ... - ["-d" options.imageFormat], "-color"}; - if (! isempty (options.maxWidth) && ! isempty (options.maxHeight)) - print_opts{end+1} = sprintf ("-S%d,%d", options.maxWidth, - options.maxHeight); - elseif (! isempty (options.maxWidth) || ! isempty (options.maxHeight)) - warning (["publish: specify both options.maxWidth ", ... - "and options.maxHeight"]); - endif - print (print_opts{:}); - fig_num++; - delete (fig_ids_new(j)); - fig_elem = cell (); - fig_elem{1} = struct ("type", "graphic", "content", fname); - if (isfield (fig_list, num2str (i))) - fig_elem = [getfield(fig_list, sprintf ("%d", i)), fig_elem]; - endif - fig_list = setfield (fig_list, sprintf ("%d", i), fig_elem); - ## Create a new figure, if there are existing plots - if (isempty (setdiff (findall (0, "type", "figure"), fig_ids)) ... - && ! isempty (fig_ids) && options.useNewFigure) - figure (); - endif - endfor - - ## Truncate output to desired length - if (options.maxOutputLines < length (doc.body{i}.output)) - doc.body{i}.output = doc.body{i}.output(1:options.maxOutputLines); - endif - doc.body{i}.output = strjoin (doc.body{i}.output, "\n"); - endif - endfor - - ## Close any figures opened by publish function - delete (setdiff (findall (0, "type", "figure"), fig_ids)); - - ## Remove temporary context - unlink (tmp_context); - - ## Insert figures to document - fig_code_blocks = fieldnames (fig_list); - body_offset = 0; - for i = 1:numel (fig_code_blocks) - elems = getfield (fig_list, fig_code_blocks{i}); - ## Compute index where the figure(s) has to be inserted - j = str2double (fig_code_blocks{i}) + body_offset; - doc.body = [doc.body(1:j), elems, doc.body(j+1:end)]; - body_offset = body_offset + numel (elems); - endfor - -endfunction - - -function cstr = eval_code_helper (context, code) - ## EVAL_CODE_HELPER evaluates a given string with Octave code in an extra - ## temporary context and returns a cellstring with the eval output. - - if (isempty (code)) - return; - endif - - load_snippet = ""; - if (exist (context, "file") == 2) - load_snippet = sprintf ('load ("%s");', context); - endif - save_snippet = sprintf ('save ("-binary", "%s");', context); - - eval (sprintf ("function __eval__ ()\n%s\n%s\n%s\nendfunction", - load_snippet, code, save_snippet)); - - cstr = strsplit (evalc ("__eval__"), "\n"); -endfunction - - -## FIXME: Missing any functional BIST tests -## FIXME: Need to create a temporary file for use with error testing - -## Test input validation -%!error publish () -%!error publish (1) -%!error <FILE does not exist> publish ("%%_non_existent_file_%%.m") -%!error <only script files can be published> publish ("publish.m") -%!error publish ("test_script.m", "format", "html", "showCode")
--- a/scripts/general/saveobj.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -## Copyright (C) 2008-2017 David Bateman -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {@var{b} =} saveobj (@var{a}) -## Method of a class to manipulate an object prior to saving it to a file. -## -## The function @code{saveobj} is called when the object @var{a} is saved -## using the @code{save} function. An example of the use of @code{saveobj} -## might be to remove fields of the object that don't make sense to be saved -## or it might be used to ensure that certain fields of the object are -## initialized before the object is saved. For example: -## -## @example -## @group -## function b = saveobj (a) -## b = a; -## if (isempty (b.field)) -## b.field = initfield (b); -## endif -## endfunction -## @end group -## @end example -## -## @seealso{loadobj, class} -## @end deftypefn - -function b = saveobj (a) - error ('saveobj: not defined for class "%s"', class (a)); -endfunction
--- a/scripts/general/validateattributes.m Thu Dec 28 15:56:09 2017 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,732 +0,0 @@ -## Copyright (C) 2013-2017 Carnë Draug -## Copyright (C) 2016 Carlo de Falco -## -## 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 -## <http://www.gnu.org/licenses/>. - -## -*- texinfo -*- -## @deftypefn {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}) -## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{arg_idx}) -## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{func_name}) -## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{func_name}, @var{arg_name}) -## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{func_name}, @var{arg_name}, @var{arg_idx}) -## Check validity of input argument. -## -## Confirms that the argument @var{A} is valid by belonging to one of -## @var{classes}, and holding all of the @var{attributes}. If it does not, -## an error is thrown, with a message formatted accordingly. The error -## message can be made further complete by the function name @var{fun_name}, -## the argument name @var{arg_name}, and its position in the input -## @var{arg_idx}. -## -## @var{classes} must be a cell array of strings (an empty cell array is -## allowed) with the name of classes (remember that a class name is case -## sensitive). In addition to the class name, the following categories -## names are also valid: -## -## @table @asis -## @item @qcode{"float"} -## Floating point value comprising classes @qcode{"double"} and -## @qcode{"single"}. -## -## @item @qcode{"integer"} -## Integer value comprising classes (u)int8, (u)int16, (u)int32, (u)int64. -## -## @item @qcode{"numeric"} -## Numeric value comprising either a floating point or integer value. -## -## @end table -## -## @var{attributes} must be a cell array with names of checks for @var{A}. -## Some of them require an additional value to be supplied right after the -## name (see details for each below). -## -## @table @asis -## @item @qcode{"<="} -## All values are less than or equal to the following value in -## @var{attributes}. -## -## @item @qcode{"<"} -## All values are less than the following value in @var{attributes}. -## -## @item @qcode{">="} -## All values are greater than or equal to the following value in -## @var{attributes}. -## -## @item @qcode{">"} -## All values are greater than the following value in @var{attributes}. -## -## @item @qcode{"2d"} -## A 2-dimensional matrix. Note that vectors and empty matrices have -## 2 dimensions, one of them being of length 1, or both length 0. -## -## @item @qcode{"3d"} -## Has no more than 3 dimensions. A 2-dimensional matrix is a 3-D matrix -## whose 3rd dimension is of length 1. -## -## @item @qcode{"binary"} -## All values are either 1 or 0. -## -## @item @qcode{"column"} -## Values are arranged in a single column. -## -## @item @qcode{"decreasing"} -## No value is @var{NaN}, and each is less than the preceding one. -## -## @item @qcode{"diag"} -## Value is a diagonal matrix. -## -## @item @qcode{"even"} -## All values are even numbers. -## -## @item @qcode{"finite"} -## All values are finite. -## -## @item @qcode{"increasing"} -## No value is @var{NaN}, and each is greater than the preceding one. -## -## @item @qcode{"integer"} -## All values are integer. This is different than using @code{isinteger} -## which only checks its an integer type. This checks that each value in -## @var{A} is an integer value, i.e., it has no decimal part. -## -## @item @qcode{"ncols"} -## Has exactly as many columns as the next value in @var{attributes}. -## -## @item @qcode{"ndims"} -## Has exactly as many dimensions as the next value in @var{attributes}. -## -## @item @qcode{"nondecreasing"} -## No value is @var{NaN}, and each is greater than or equal to the preceding -## one. -## -## @item @qcode{"nonempty"} -## It is not empty. -## -## @item @qcode{"nonincreasing"} -## No value is @var{NaN}, and each is less than or equal to the preceding one. -## -## @item @qcode{"nonnan"} -## No value is a @code{NaN}. -## -## @item @nospell{@qcode{"nonnegative"}} -## All values are non negative. -## -## @item @qcode{"nonsparse"} -## It is not a sparse matrix. -## -## @item @qcode{"nonzero"} -## No value is zero. -## -## @item @qcode{"nrows"} -## Has exactly as many rows as the next value in @var{attributes}. -## -## @item @qcode{"numel"} -## Has exactly as many elements as the next value in @var{attributes}. -## -## @item @qcode{"odd"} -## All values are odd numbers. -## -## @item @qcode{"positive"} -## All values are positive. -## -## @item @qcode{"real"} -## It is a non-complex matrix. -## -## @item @qcode{"row"} -## Values are arranged in a single row. -## -## @item @qcode{"scalar"} -## It is a scalar. -## -## @item @qcode{"size"} -## Its size has length equal to the values of the next in @var{attributes}. -## The next value must is an array with the length for each dimension. To -## ignore the check for a certain dimension, the value of @code{NaN} can be -## used. -## -## @item @qcode{"square"} -## Is a square matrix. -## -## @item @qcode{"vector"} -## Values are arranged in a single vector (column or vector). -## -## @end table -## -## @seealso{isa, validatestring, inputParser} -## @end deftypefn - -function validateattributes (A, cls, attr, varargin) - - if (nargin < 3 || nargin > 6) - print_usage (); - elseif (! iscellstr (cls)) - error ("Octave:invalid-type", - "validateattributes: CLASSES must be a cell array of strings"); - elseif (! iscell (attr)) - error ("Octave:invalid-type", - "validateattributes: ATTRIBUTES must be a cell array"); - endif - - ## Built start of error message from the extra optional arguments - func_name = ""; - var_name = "input"; - if (nargin > 3) - fourth = varargin{1}; - if (ischar (fourth)) - func_name = [fourth ": "]; - elseif (nargin == 4 && valid_arg_idx (fourth)) - var_name = sprintf ("input %d", fourth); - else - error ("Octave:invalid-input-arg", - "validateattributes: 4th input argument must be ARG_IDX or FUNC_NAME"); - endif - - if (nargin > 4) - var_name = varargin{2}; - if (! ischar (var_name)) - error ("Octave:invalid-type", - "validateattributes: VAR_NAME must be a string"); - endif - - if (nargin > 5) - arg_idx = varargin{3}; - if (! valid_arg_idx (arg_idx)) - error ("Octave:invalid-input-arg", - "validateattributes: ARG_IDX must be a positive integer"); - endif - var_name = sprintf ("%s (argument #%i)", var_name, arg_idx); - endif - endif - endif - err_ini = [func_name var_name]; - - check_cl = isa (A, cls); - if (! isempty (check_cl) && ! any (check_cl)) - ## Allowing for an empty list of classes is Matlab incompatible but - ## that should count as a just a Matlab bug, not an incompatibility. - - ## Replace the category names with the classes that belong to it. - integer = { "int8" "int16" "int32" "int64" ... - "uint8" "uint16" "uint32" "uint64"}; - float = {"single" "double"}; - numeric = {integer{:} float{:}}; - cls = replace_cl_group (cls, "integer", integer); - cls = replace_cl_group (cls, "float", float ); - cls = replace_cl_group (cls, "numeric", numeric); - cls = unique (cls); - - classes = sprintf (" %s", cls{:}); - error ("Octave:invalid-type", - "%s must be of class:\n\n %s\n\nbut was of class %s", - err_ini, classes, class (A)); - endif - - ## We use a while loop because some attributes require the following value - ## in the cell array. Also, we can't just get the boolean value for the - ## test and check at the end the error message since some of the tests - ## require some more complex error message. - - ## It may look like that we don't perform enough input check in this - ## function (e.g., we don't check if there's a value after the size - ## attribute). The reasoning is that this will be a function mostly used - ## by developers with fairly static input so any problem would be caught - ## immediately during that function development, it's no dependent on the - ## final user input. In addition, it can be called so many times at the - ## start of every function, we want it to run specially fast. - idx = 1; - problem = false; # becomes true when one of the tests fails - while (idx <= numel (attr)) - ## FIXME: once we use this in Octave core, it might be worthy to find - ## which attributes are checked more often, and place them in that - ## order inside the switch block. - switch (tolower (attr{idx++})) - case "2d", - problem = ndims (A) != 2; - err_id = "Octave:expected-2d"; - case "3d", - problem = ndims (A) > 3; - err_id = "Octave:expected-3d"; - case "column", - problem = ! iscolumn (A); - err_id = "Octave:expected-column"; - case "row", - problem = ! isrow (A); - err_id = "Octave:expected-row"; - case "scalar", - problem = ! isscalar (A); - err_id = "Octave:expected-scalar"; - case "vector", - problem = ! isvector (A); - err_id = "Octave:expected-vector"; - case "square", - problem = ! issquare (A); - err_id = "Octave:expected-square"; - case "diag", - problem = ! isdiag (A); - err_id = "Octave:expected-diag"; - case "nonempty", - problem = isempty (A); - err_id = "Octave:expected-nonempty"; - case "nonsparse", - problem = issparse (A); - err_id = "Octave:expected-nonsparse"; - case "binary", - problem = ! islogical (A) && ... - any ((A(:) != 1) & (A(:) != 0)); - err_id = "Octave:expected-binary"; - case "even", - problem = any (rem (A(:), 2) != 0); - err_id = "Octave:expected-even"; - case "odd", - problem = any (mod (A(:), 2) != 1); - err_id = "Octave:expected-odd"; - case "integer", - problem = ! isinteger (A) && ... - any (ceil (A(:)) != A(:)); - err_id = "Octave:expected-integer"; - case "real", - problem = ! isreal (A); - err_id = "Octave:expected-real"; - case "finite", - problem = ! isinteger (A) && ... - ! all (isfinite (A(:))); - err_id = "Octave:expected-finite"; - case "nonnan", - problem = ! isinteger (A) && ... - any (isnan (A(:))); - err_id = "Octave:expected-nonnan"; - case "nonnegative", - problem = any (A(:) < 0); - err_id = "Octave:expected-nonnegative"; - case "nonzero", - problem = any (A(:) == 0); - err_id = "Octave:expected-nonzero"; - case "positive", - problem = any (A(:) <= 0); - err_id = "Octave:expected-positive"; - case "decreasing", - problem = (any (isnan (A(:))) - || any (diff (A(:)) >= 0)); - err_id = "Octave:expected-decreasing"; - case "increasing", - problem = (any (isnan (A(:))) - || any (diff (A(:)) <= 0)); - err_id = "Octave:expected-increasing"; - case "nondecreasing", - problem = (any (isnan (A(:))) - || any (diff (A(:)) < 0)); - err_id = "Octave:expected-nondecreasing"; - case "nonincreasing", - problem = (any (isnan (A(:))) - || any (diff (A(:)) > 0)); - err_id = "Octave:expected-nonincreasing"; - case "size", - A_size = size (A); - w_size = attr{idx++}; - A_size(isnan (w_size)) = NaN; - if (! isequaln (A_size, w_size)) - A_size_str = sprintf ("%dx", size (A))(1:end-1); - w_size_str = sprintf ("%ix", w_size)(1:end-1); - w_size_str = strrep (w_size_str, "NaN", "N"); - err_id = "Octave:incorrect-size"; - error (err_id, - "%s must be of size %s but was %s", - err_ini, w_size_str, A_size_str); - endif - case "numel", - if (numel (A) != attr{idx++}) - err_id = "Octave:incorrect-numel"; - error (err_id, - "%s must have %d elements", err_ini, attr{idx-1}); - endif - case "ncols", - if (columns (A) != attr{idx++}) - err_id = "Octave:incorrect-numcols"; - error (err_id, - "%s must have %d columns", err_ini, attr{idx-1}); - endif - case "nrows", - if (rows (A) != attr{idx++}) - err_id = "Octave:incorrect-numrows"; - error (err_id, - "%s must have %d rows", err_ini, attr{idx-1}); - endif - case "ndims", - ## Note that a [4 5 1] matrix is not considered to have ndims == 3 - ## but is ok for "3d". This is not a bug. - if (ndims (A) != attr{idx++}) - err_id = "Octave:incorrect-numdims"; - error (err_id, - "%s must have %d dimensions", err_ini, attr{idx-1}); - endif - case ">" - if (! all (A(:) > attr{idx++})) - err_id = "Octave:expected-greater"; - error (err_id, - "%s must be greater than %f", err_ini, attr{idx-1}); - endif - case ">=" - if (! all (A(:) >= attr{idx++})) - err_id = "Octave:expected-greater-equal"; - error (err_id, - "%s must be greater than or equal to %f", err_ini, attr{idx-1}); - endif - case "<" - if (! all (A(:) < attr{idx++})) - err_id = "Octave:expected-less"; - error (err_id, - "%s must be less than %f", err_ini, attr{idx-1}); - endif - case "<=" - if (! all (A(:) <= attr{idx++})) - err_id = "Octave:expected-less-equal"; - error (err_id, - "%s must be less than or equal to %f", err_ini, attr{idx-1}); - endif - otherwise - err_id = "Octave:invalid-input-arg"; - error (err_id, - "validateattributes: unknown ATTRIBUTE %s", attr{idx-1}); - endswitch - if (problem) - error (err_id, - "%s must be %s", err_ini, attr{idx-1}); - endif - endwhile - -endfunction - -function retval = valid_arg_idx (arg) - retval = isnumeric (arg) && isscalar (arg) && arg > 0 && arg == fix (arg); -endfunction - -function cls = replace_cl_group (cls, name, group) - num_pos = strcmpi (cls, name); - if (any (num_pos)) - cls(num_pos) = []; - cls(end+1:end+numel(group)) = group; - endif -endfunction - - -%!error <double> validateattributes (rand (5), {"uint8"}, {}) -%!error <single> validateattributes (uint8 (rand (5)), {"float"}, {}) -%!error <2d> validateattributes (rand (5, 5, 5), {}, {"2d"}) -%!error <3d> validateattributes (rand (5, 5, 5, 7), {}, {"3d"}) -%!error <column> validateattributes (rand (5, 5), {}, {"column"}) -%!error <column> validateattributes (rand (1, 5), {}, {"column"}) -%!error <row> validateattributes (rand (5, 5), {}, {"row"}) -%!error <row> validateattributes (rand (5, 1), {}, {"row"}) -%!error <scalar> validateattributes (rand (1, 5), {}, {"scalar"}) -%!error <vector> validateattributes (rand (5), {}, {"vector"}) -%!error <square> validateattributes (rand (5, 6), {}, {"square"}) -%!error <nonempty> validateattributes ([], {}, {"nonempty"}) -%!error <nonsparse> validateattributes (sparse(rand(5)), {}, {"nonsparse"}) -%!error <binary> validateattributes ("text", {}, {"binary"}) -%!error <binary> validateattributes ([0 1 0 3 0], {}, {"binary"}) -%!error <even> validateattributes ([2 3 6 8], {}, {"even"}) -%!error <even> validateattributes ([2 NaN], {}, {"even"}) -%!error <odd> validateattributes ([3 4 7 5], {}, {"odd"}) -%!error <odd> validateattributes ([5 NaN], {}, {"odd"}) -%!error <integer> validateattributes ([5 5.2 5.7], {}, {"integer"}) -%!error <real> validateattributes ([5i 8 9], {}, {"real"}) -%!error <finite> validateattributes ([5i Inf 8], {}, {"finite"}) -%!error <nonnan> validateattributes ([NaN Inf 8], {}, {"nonnan"}) -%!error <nonnegative> validateattributes ([7 8 -9], {}, {"nonnegative"}) -%!error <nonzero> validateattributes ([7 8 0], {}, {"nonzero"}) -%!error <positive> validateattributes ([7 0 8], {}, {"positive"}) -%!error <decreasing> validateattributes ([7 8 4 3 -5], {}, {"decreasing"}) -%!error <decreasing> validateattributes ([7 NaN 4 3 -5], {}, {"decreasing"}) -%!error <increasing> validateattributes ([7 8 4 9 20], {}, {"increasing"}) -%!error <increasing> validateattributes ([7 8 NaN 9 20], {}, {"increasing"}) -%!error <nonincreasing> validateattributes ([7 8 4 9 20], {}, {"nonincreasing"}) -%!error <nonincreasing> validateattributes ([7 8 NaN 9 20], {}, {"nonincreasing"}) -%!error <nondecreasing> validateattributes ([7 8 4 3 -5], {}, {"nondecreasing"}) -%!error <nondecreasing> validateattributes ([7 NaN 4 3 -5], {}, {"nondecreasing"}) -%!error <size> validateattributes (ones (5, 3, 6), {}, {"size", [5 4 7]}) -%!error <size> validateattributes (ones (5, 3, 6), {}, {"size", [5 NaN 7]}) -%!error <size> validateattributes (ones (5, 3, 6), {}, {"size", [5 3 6 2]}) -%!error <elements> validateattributes (ones (6, 3), {}, {"numel", 12}) -%!error <columns> validateattributes (ones (6, 2), {}, {"ncols", 3}) -%!error <rows> validateattributes (ones (6, 2), {}, {"nrows", 3}) -%!error <dimensions> validateattributes (ones (6, 2, 6, 3), {}, {"ndims", 3}) -%!error <greater than> validateattributes ([6 7 8 5], {}, {">", 5}) -%!error <greater than> validateattributes ([6 7 8 5], {}, {">=", 6}) -%!error <less than> validateattributes ([6 7 8 5], {}, {"<", 8}) -%!error <less than> validateattributes ([6 7 8 5], {}, {"<=", 7}) -%!error <diag> validateattributes ([0 0 0; 0 0 0; 1 0 0], {}, {"diag"}) -%!error <diag> validateattributes (repmat (eye (3), [1 1 3]), {}, {"diag"}) - -%!test -%! validateattributes (rand (5), {"numeric"}, {}); -%! validateattributes (rand (5), {"float"}, {}); -%! validateattributes (rand (5), {"double"}, {}); -%! validateattributes ("text", {"char"}, {}); -%! validateattributes (rand (5), {}, {"2d"}); -%! validateattributes (rand (5), {}, {"3d"}); -%! validateattributes (rand (5, 5, 5), {}, {"3d"}); -%! validateattributes (rand (5, 1), {}, {"column"}); -%! validateattributes (rand (1, 5), {}, {"row"}); -%! validateattributes ("a", {}, {"scalar"}); -%! validateattributes (5, {}, {"scalar"}); -%! validateattributes (rand (1, 5), {}, {"vector"}); -%! validateattributes (rand (5, 1), {}, {"vector"}); -%! validateattributes (rand (5), {}, {"square"}); -%! validateattributes (rand (5), {}, {"nonempty"}); -%! validateattributes (rand (5), {}, {"nonsparse"}); -%! validateattributes ([0 1 0 1 0], {}, {"binary"}); -%! validateattributes (rand (5) > 0.5, {}, {"binary"}); -%! validateattributes ([8 4 0 6], {}, {"even"}); -%! validateattributes ([-1 3 5], {}, {"odd"}); -%! validateattributes ([8 4 0 6], {}, {"real"}); -%! validateattributes ([8 4i 0 6], {}, {"finite"}); -%! validateattributes (uint8 ([8 4]), {}, {"finite"}); -%! validateattributes ([8 Inf], {}, {"nonnan"}); -%! validateattributes ([0 7 4], {}, {"nonnegative"}); -%! validateattributes ([-8 7 4], {}, {"nonzero"}); -%! validateattributes ([8 7 4], {}, {"positive"}); -%! validateattributes ([8 7 4 -5], {}, {"decreasing"}); -%! validateattributes ([-8 -7 4 5], {}, {"increasing"}); -%! validateattributes ([8 4 4 -5], {}, {"nonincreasing"}); -%! validateattributes ([-8 -8 4 5], {}, {"nondecreasing"}); -%! validateattributes (rand (4, 6, 7, 2), {}, {"size", [4 6 7 2]}); -%! validateattributes (rand (4, 6, 7, 2), {}, {"size", [4 NaN 7 2]}); -%! validateattributes (rand (4, 6, 7, 2), {}, {"size", [4 6 NaN 2 NaN]}); -%! validateattributes (rand (6, 2), {}, {"numel", 12}); -%! validateattributes (rand (6, 2), {}, {"ncols", 2}); -%! validateattributes (rand (6, 2), {}, {"nrows", 6}); -%! validateattributes (rand (6, 2, 4, 5), {}, {"ndims", 4}); -%! validateattributes ([4 5 6 7], {}, {">", 3}); -%! validateattributes ([4 5 6 7], {}, {">=", 4}); -%! validateattributes ([4 5 6 7], {}, {"<", 8}); -%! validateattributes ([4 5 6 7], {}, {"<=", 7}); -%! validateattributes (eye (3), {}, {"diag"}); -%! validateattributes ([1 0 0; 0 1 0; 0 0 1], {}, {"diag"}); -%! validateattributes (zeros (3), {}, {"diag"}); - -%!test -%! validateattributes ([0 1 0 1], {"double", "uint8"}, {"binary", "size", [NaN 4], "nonnan"}); - -%!test -%! try validateattributes (ones(1,2,3), {"numeric"}, {"2d"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-2d"); -%! end_try_catch - -%!test -%! try validateattributes (ones(1,2,3,4), {"numeric"}, {"3d"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-3d"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"column"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-column"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2].', {"numeric"}, {"row"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-row"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"scalar"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-scalar"); -%! end_try_catch - -%!test -%! try validateattributes (ones(3), {"numeric"}, {"vector"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-vector"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"size", [1 1]}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:incorrect-size"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"numel", 7}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:incorrect-numel"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"ncols", 7}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:incorrect-numcols"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"nrows", 7}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:incorrect-numrows"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"ndims", 5}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:incorrect-numdims"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"square"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-square"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"diag"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-diag"); -%! end_try_catch - -%!test -%! try validateattributes ([], {"numeric"}, {"nonempty"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nonempty"); -%! end_try_catch - -%!test -%! try validateattributes (speye(2), {"numeric"}, {"nonsparse"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nonsparse"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {">", 3}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-greater"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {">=", 3}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-greater-equal"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"<", -3}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-less"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"<=", -3}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-less-equal"); -%! end_try_catch - -%!test -%! try validateattributes (3, {"numeric"}, {"binary"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-binary"); -%! end_try_catch - -%!test -%! try validateattributes (1, {"numeric"}, {"even"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-even"); -%! end_try_catch - -%!test -%! try validateattributes (2, {"numeric"}, {"odd"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-odd"); -%! end_try_catch - -%!test -%! try validateattributes (1.1, {"numeric"}, {"integer"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-integer"); -%! end_try_catch - -%!test -%! try validateattributes (1+1i*2, {"numeric"}, {"real"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-real"); -%! end_try_catch - -%!test -%! try validateattributes (Inf, {"numeric"}, {"finite"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-finite"); -%! end_try_catch - -%!test -%! try validateattributes (NaN, {"numeric"}, {"nonnan"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nonnan"); -%! end_try_catch - -%!test -%! try validateattributes (-1, {"numeric"}, {"nonnegative"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nonnegative"); -%! end_try_catch - -%!test -%! try validateattributes (0, {"numeric"}, {"nonzero"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nonzero"); -%! end_try_catch - -%!test -%! try validateattributes (-1, {"numeric"}, {"positive"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-positive"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"decreasing"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-decreasing"); -%! end_try_catch - -%!test -%! try validateattributes ([2 1], {"numeric"}, {"increasing"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-increasing"); -%! end_try_catch - -%!test -%! try validateattributes ([1 0], {"numeric"}, {"nondecreasing"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nondecreasing"); -%! end_try_catch - -%!test -%! try validateattributes ([1 2], {"numeric"}, {"nonincreasing"}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:expected-nonincreasing"); -%! end_try_catch - -%!test -%! try validateattributes (@sin, {"numeric"}, {}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:invalid-type"); -%! end_try_catch - -%!test -%! try validateattributes (@sin, 1, {}); -%! catch id, -%! assert (getfield (id, "identifier"), "Octave:invalid-type"); -%! end_try_catch
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/fieldnames.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,92 @@ +## Copyright (C) 2012-2017 Rik Wehbring +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {@var{names} =} fieldnames (@var{struct}) +## @deftypefnx {} {@var{names} =} fieldnames (@var{obj}) +## @deftypefnx {} {@var{names} =} fieldnames (@var{javaobj}) +## @deftypefnx {} {@var{names} =} fieldnames ("@var{javaclassname}") +## Return a cell array of strings with the names of the fields in the +## specified input. +## +## When the input is a structure @var{struct}, the names are the elements of +## the structure. +## +## When the input is an Octave object @var{obj}, the names are the public +## properties of the object. +## +## When the input is a Java object @var{javaobj} or a string containing the +## name of a Java class @var{javaclassname}, the names are the public fields +## (data members) of the object or class. +## @seealso{numfields, isfield, orderfields, struct, methods} +## @end deftypefn + +function names = fieldnames (obj) + + if (nargin != 1) + print_usage (); + endif + + if (isstruct (obj) || isobject (obj)) + ## Call internal C++ function for structs or Octave objects + names = __fieldnames__ (obj); + elseif (isjava (obj) || ischar (obj)) + ## FIXME: Function prototype that accepts java obj exists, but doesn't + ## work if obj is java.lang.String. Convert obj to classname. + ## FIXME: this is now working for objects whose class is in the dynamic + ## classpath but will continue to fail if such classnames are used + ## instead (see bug #42710). + if (isa (obj, "java.lang.String")) + obj = class (obj); + endif + names_str = javaMethod ("getFields", "org.octave.ClassHelper", obj); + names = ostrsplit (names_str, ';'); + else + error ("fieldnames: Invalid input argument"); + endif + +endfunction + + +## test preservation of fieldname order +%!test +%! x(3).d=1; x(2).a=2; x(1).b=3; x(2).c=3; +%! assert (fieldnames (x), {"d"; "a"; "b"; "c"}); + +## test empty structure +%!test +%! s = struct (); +%! assert (fieldnames (s), cell (0, 1)); + +## test Java classname by passing classname +%!testif HAVE_JAVA; usejava ("jvm") +%! names = fieldnames ("java.lang.Double"); +%! assert (any (strcmp (names, "MAX_VALUE"))); + +## test Java classname by passing java object +%!testif HAVE_JAVA; usejava ("jvm") +%! names = fieldnames (javaObject ("java.lang.Double", 10)); +%! assert (any (strcmp (names, "MAX_VALUE"))); +%! assert (all (ismember ({"POSITIVE_INFINITY", "NEGATIVE_INFINITY", ... +%! "NaN", "MAX_VALUE", "MIN_NORMAL", "MIN_VALUE", ... +%! "MAX_EXPONENT", "MIN_EXPONENT", "SIZE", "TYPE"}, +%! names))); + +%!testif HAVE_JAVA; usejava ("jvm") +%! names = fieldnames (javaObject ("java.lang.String", "Hello")); +%! assert (any (strcmp (names, "CASE_INSENSITIVE_ORDER")));
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/grabcode.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,97 @@ +## Copyright (C) 2016-2017 Kai T. Ohlhus <k.ohlhus@gmail.com> +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} grabcode (@var{url}) +## @deftypefnx {} {} grabcode (@var{filename}) +## @deftypefnx {} {@var{code_str} =} grabcode (@dots{}) +## +## Grab the code from a report created by the @code{publish} function. +## +## The grabbed code inside the published report must be enclosed by the +## strings @samp{##### SOURCE BEGIN #####} and @samp{##### SOURCE END #####}. +## The @code{publish} function creates this format automatically. +## +## If no return value is requested the code is saved to a temporary file and +## opened in the default editor. NOTE: The temporary file must be saved under +## a new or the code will be lost. +## +## If an output is requested the grabbed code will be returned as string +## @var{code_str}. +## +## Example: +## +## @example +## @group +## publish ("my_script.m"); +## grabcode ("html/my_script.html"); +## @end group +## @end example +## +## The example above publishes @file{my_script.m} to the default location +## @file{html/my_script.html}. Next, the published Octave script is grabbed to +## edit its content in a new temporary file. +## +## @seealso{publish} +## @end deftypefn + +function code_str = grabcode (url) + + if (nargin != 1) + print_usage (); + endif + + if (exist (url) == 2) + ## URL is a local file + oct_code = fileread (url); + else + ## Otherwise, try to read remote URL + [oct_code, success, message] = urlread (url); + if (! success) + error (["grabcode: " message]); + endif + endif + + ## Extract relevant part + oct_code = regexp (oct_code, ... + '##### SOURCE BEGIN #####\n(.*)##### SOURCE END #####', "once", "tokens"); + oct_code = oct_code{1}; + + if (nargout == 1) + code_str = oct_code; + else + ## Open temporary file in editor + fname = [tempname() ".m"]; + fid = fopen (fname, "w"); + if (fid < 0) + error ("grabcode: could not open temporary file"); + endif + fprintf (fid, "%s", oct_code); + fclose (fid); + edit (fname); + warndlg (["grabcode: Make sure to save the temporary file\n\n\t", ... + fname, "\n\nto a location of your choice. ", ... + "Otherwise all grabbed code will be lost!"]); + endif + +endfunction + + +## Test input validation +%!error grabcode () +%!error grabcode (1,2)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/inputParser.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,826 @@ +## Copyright (C) 2011-2017 Carnë Draug +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- 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 @command{addRequired}); +## +## @item optional (see @command{addOptional}); +## +## @item named (see @command{addParameter}); +## +## @item switch (see @command{addSwitch}). +## @end enumerate +## +## After defining the function API with these methods, the supplied arguments +## can be parsed with the @command{parse} method and the parsing results +## accessed with the @command{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 @command{Results}, but for unmatched parameters. +## See the @command{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 +## @command{Switch} and @command{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 @command{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 +## @group +## 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 group +## @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. @command{Required} arguments must be +## first and can be followed by any @command{Optional} arguments. Only +## the @command{Parameter} and @command{Switch} arguments may be mixed +## together and they must appear at the end. +## +## @emph{Note 2}: If both @command{Optional} and @command{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 @command{Optional} +## arguments. The remaining arguments will be compared against any +## @command{Parameter} or @command{Switch} arguments. +## +## @seealso{nargin, validateattributes, validatestring, varargin} +## @end deftypefn + +## -*- 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 @command{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 @command{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 + +## -*- 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 @command{help inputParser} for examples. +## +## @end deftypefn + +## -*- 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 @command{addParameter} method without the +## @qcode{"PartialMatchPriority"} option. See it for the help text. +## +## @end deftypefn + +## -*- 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 @command{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 @command{help inputParser} for examples. +## +## @emph{Note}: this can be used together with the other type of arguments but +## it must be the first (see @command{@@inputParser}). +## +## @end deftypefn + +## -*- 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 @command{help inputParser} for examples. +## +## @end deftypefn + +## -*- 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 @command{Results} +## accessor. See @command{help inputParser} for a more complete description. +## +## @end deftypefn + +## Author: Carnë Draug <carandraug@octave.org> + +classdef inputParser < handle + 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) + if (nargin < 2 || nargin > 3) + 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) + if (nargin < 3 || nargin > 4) + 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) + if (nargin < 3 || nargin > 4) + print_usage (); + endif + this.addParameter (name, def, val); + endfunction + + function addParameter (this, name, def, varargin) + 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) + if (nargin != 2) + print_usage (); + endif + this.validate_name ("Switch", name); + this.Switch.(name).def = false; + endfunction + + function parse (this, varargin) + 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)]'(:); + 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) + if (this.CaseSensitive) + r = isfield (this.(type), name); + this.last_name = name; + else + fnames = this.([type "Names"]); + l = strcmpi (name, fnames); + r = any (l(:)); + if (r) + this.last_name = fnames{l}; + 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);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/isdir.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,41 @@ +## Copyright (C) 2004-2017 Alois Schloegl +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} isdir (@var{f}) +## Return true if @var{f} is a directory. +## @seealso{exist, stat, is_absolute_filename, is_rooted_relative_filename} +## @end deftypefn + +function retval = isdir (f) + + if (nargin != 1) + print_usage (); + endif + + ## Exist returns an integer but isdir should return a logical. + retval = (exist (f, "dir") == 7); + +endfunction + + +%!assert (isdir (pwd ())) +%!assert (! isdir ("this is highly unlikely to be a directory name")) + +%!error isdir () +%!error isdir (1, 2)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/loadobj.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,42 @@ +## Copyright (C) 2008-2017 David Bateman +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {@var{b} =} loadobj (@var{a}) +## Method of a class to manipulate an object after loading it from a file. +## +## The function @code{loadobj} is called when the object @var{a} is loaded +## using the @code{load} function. An example of the use of @code{saveobj} +## might be to add fields to an object that don't make sense to be saved. +## For example: +## +## @example +## @group +## function b = loadobj (a) +## b = a; +## b.addmissingfield = addfield (b); +## endfunction +## @end group +## @end example +## +## @seealso{saveobj, class} +## @end deftypefn + +function b = loadobj (a) + error ('loadobj: not defined for class "%s"', class (a)); +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/methods.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,86 @@ +## Copyright (C) 2012-2017 Rik Wehbring +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} methods (@var{obj}) +## @deftypefnx {} {} methods ("@var{classname}") +## @deftypefnx {} {@var{mtds} =} methods (@dots{}) +## List the names of the public methods for the object @var{obj} or the +## named class @var{classname}. +## +## @var{obj} may be an Octave class object or a Java object. +## @var{classname} may be the name of an Octave class or a Java class. +## +## When called with no output arguments, @code{methods} prints the list of +## method names to the screen. Otherwise, the output argument @var{mtds} +## contains the list in a cell array of strings. +## @seealso{fieldnames} +## @end deftypefn + +function mtds = methods (obj) + + if (nargin != 1) + print_usage (); + endif + + if (isobject (obj)) + ## Call internal C++ function for Octave objects + mtds_list = __methods__ (obj); + elseif (ischar (obj)) + ## Could be a classname for an Octave class or Java class. + ## Try Octave class first. + mtds_list = __methods__ (obj); + if (isempty (mtds_list)) + mtds_str = javaMethod ("getMethods", "org.octave.ClassHelper", obj); + mtds_list = ostrsplit (mtds_str, ';'); + endif + elseif (isjava (obj)) + ## FIXME: Function prototype accepts java obj, but doesn't work if obj + ## is e.g., java.lang.String. Convert obj to classname then. + try + mtds_str = javaMethod ("getMethods", "org.octave.ClassHelper", obj); + catch + obj = class (obj); + mtds_str = javaMethod ("getMethods", "org.octave.ClassHelper", obj); + end_try_catch + mtds_list = strsplit (mtds_str, ';'); + else + error ("methods: Invalid input argument"); + endif + + if (nargout == 0) + classname = ifelse (ischar (obj), obj, class (obj)); + printf ("Methods for class %s:\n", classname); + disp (list_in_columns (mtds_list)); + else + mtds = mtds_list; + endif + +endfunction + + +## test Octave classname +%!test +%! mtds = methods ("ftp"); +%! assert (mtds{1}, "ascii"); + +## test Java classname +%!testif HAVE_JAVA; usejava ("jvm") +%! mtds = methods ("java.lang.Double"); +%! search = strfind (mtds, "java.lang.Double valueOf"); +%! assert (! isempty ([search{:}]));
--- a/scripts/miscellaneous/module.mk Thu Dec 28 15:56:09 2017 -0800 +++ b/scripts/miscellaneous/module.mk Thu Dec 28 16:14:37 2017 -0800 @@ -3,6 +3,8 @@ %reldir%/private %canon_reldir%_PRIVATE_FCN_FILES = \ + %reldir%/private/__publish_html_output__.m \ + %reldir%/private/__publish_latex_output__.m \ %reldir%/private/__w2mpth__.m \ %reldir%/private/display_info_file.m @@ -20,38 +22,50 @@ %reldir%/dos.m \ %reldir%/edit.m \ %reldir%/fact.m \ + %reldir%/fieldnames.m \ %reldir%/fileattrib.m \ %reldir%/fileparts.m \ %reldir%/fullfile.m \ %reldir%/genvarname.m \ %reldir%/getfield.m \ + %reldir%/grabcode.m \ %reldir%/gunzip.m \ %reldir%/info.m \ %reldir%/inputname.m \ + %reldir%/inputParser.m \ %reldir%/isdeployed.m \ + %reldir%/isdir.m \ %reldir%/ismac.m \ %reldir%/ispc.m \ %reldir%/isunix.m \ %reldir%/license.m \ %reldir%/list_primes.m \ + %reldir%/loadobj.m \ %reldir%/ls.m \ %reldir%/ls_command.m \ %reldir%/menu.m \ + %reldir%/methods.m \ %reldir%/mex.m \ %reldir%/mexext.m \ %reldir%/mkdir.m \ %reldir%/mkoctfile.m \ %reldir%/movefile.m \ %reldir%/namelengthmax.m \ + %reldir%/nargchk.m \ + %reldir%/narginchk.m \ + %reldir%/nargoutchk.m \ %reldir%/news.m \ + %reldir%/nthargout.m \ %reldir%/open.m \ %reldir%/orderfields.m \ %reldir%/pack.m \ %reldir%/parseparams.m \ %reldir%/perl.m \ + %reldir%/publish.m \ %reldir%/python.m \ %reldir%/recycle.m \ %reldir%/run.m \ + %reldir%/saveobj.m \ %reldir%/setfield.m \ %reldir%/substruct.m \ %reldir%/swapbytes.m \ @@ -66,6 +80,7 @@ %reldir%/ver.m \ %reldir%/version.m \ %reldir%/what.m \ + %reldir%/validateattributes.m \ %reldir%/zip.m %canon_reldir%dir = $(fcnfiledir)/miscellaneous
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/nargchk.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,83 @@ +## Copyright (C) 2008-2017 Bill Denney +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {@var{msgstr} =} nargchk (@var{minargs}, @var{maxargs}, @var{nargs}) +## @deftypefnx {} {@var{msgstr} =} nargchk (@var{minargs}, @var{maxargs}, @var{nargs}, "string") +## @deftypefnx {} {@var{msgstruct} =} nargchk (@var{minargs}, @var{maxargs}, @var{nargs}, "struct") +## Return an appropriate error message string (or structure) if the number of +## inputs requested is invalid. +## +## This is useful for checking to see that the number of input arguments +## supplied to a function is within an acceptable range. +## +## @strong{Caution}: @code{nargchk} is scheduled for deprecation. Use +## @code{narginchk} in all new code. +## @seealso{narginchk, nargoutchk, error, nargin, nargout} +## @end deftypefn + +## Author: Bill Denney <bill@denney.ws> + +function msg = nargchk (minargs, maxargs, nargs, outtype = "string") + + if (nargin < 3 || nargin > 4) + print_usage (); + elseif (minargs > maxargs) + error ("nargchk: MINARGS must be <= MAXARGS"); + elseif (! any (strcmpi (outtype, {"string", "struct"}))) + error ('nargchk: output type must be either "string" or "struct"'); + elseif (! (isscalar (minargs) && isscalar (maxargs) && isscalar (nargs))) + error ("nargchk: MINARGS, MAXARGS, and NARGS must be scalars"); + endif + + msg = struct ("message", "", "identifier", ""); + if (nargs < minargs) + msg.message = "not enough input arguments"; + msg.identifier = "Octave:nargchk:not-enough-inputs"; + elseif (nargs > maxargs) + msg.message = "too many input arguments"; + msg.identifier = "Octave:nargchk:too-many-inputs"; + endif + + if (strcmpi (outtype, "string")) + msg = msg.message; + elseif (isempty (msg.message)) + ## Compatability: Matlab returns a 0x1 empty struct when nargchk passes + msg = resize (msg, 0, 1); + endif + +endfunction + + +## Tests +%!shared stnul, stmin, stmax +%! stnul = resize (struct ("message", "", "identifier", ""), 0, 1); +%! stmin = struct ("message", "not enough input arguments", +%! "identifier", "Octave:nargchk:not-enough-inputs"); +%! stmax = struct ("message", "too many input arguments", +%! "identifier", "Octave:nargchk:too-many-inputs"); +%!assert (nargchk (0, 1, 0), "") +%!assert (nargchk (0, 1, 1), "") +%!assert (nargchk (1, 1, 0), "not enough input arguments") +%!assert (nargchk (0, 1, 2), "too many input arguments") +%!assert (nargchk (0, 1, 2, "string"), "too many input arguments") +## Struct outputs +%!assert (nargchk (0, 1, 0, "struct"), stnul) +%!assert (nargchk (0, 1, 1, "struct"), stnul) +%!assert (nargchk (1, 1, 0, "struct"), stmin) +%!assert (nargchk (0, 1, 2, "struct"), stmax)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/narginchk.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,71 @@ +## Copyright (C) 2012-2017 Carnë Draug +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} narginchk (@var{minargs}, @var{maxargs}) +## Check for correct number of input arguments. +## +## Generate an error message if the number of arguments in the calling function +## is outside the range @var{minargs} and @var{maxargs}. Otherwise, do +## nothing. +## +## Both @var{minargs} and @var{maxargs} must be scalar numeric values. Zero, +## Inf, and negative values are all allowed, and @var{minargs} and +## @var{maxargs} may be equal. +## +## Note that this function evaluates @code{nargin} on the caller. +## +## @seealso{nargoutchk, error, nargout, nargin} +## @end deftypefn + +## Author: Carnë Draug <carandraug+dev@gmail.com> + +function narginchk (minargs, maxargs) + + if (nargin != 2) + print_usage; + elseif (! isnumeric (minargs) || ! isscalar (minargs)) + error ("narginchk: MINARGS must be a numeric scalar"); + elseif (! isnumeric (maxargs) || ! isscalar (maxargs)) + error ("narginchk: MAXARGS must be a numeric scalar"); + elseif (minargs > maxargs) + error ("narginchk: MINARGS cannot be larger than MAXARGS"); + endif + + args = evalin ("caller", "nargin;"); + + if (args < minargs) + error ("narginchk: not enough input arguments"); + elseif (args > maxargs) + error ("narginchk: too many input arguments"); + endif + +endfunction + + +%!function f (nargs, varargin) +%! narginchk (nargs(1), nargs(2)); +%!endfunction + +%!error <too many input arguments> f([0,0]) +%!error <not enough input arguments> f([3, 3], 1) + +%!test +%! f([1,1]); +%!test +%! f([1,5], 2, 3, 4, 5);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/nargoutchk.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,124 @@ +## Copyright (C) 2008-2017 Bill Denney +## Copyright (C) 2012 Carnë Draug +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} nargoutchk (@var{minargs}, @var{maxargs}) +## @deftypefnx {} {@var{msgstr} =} nargoutchk (@var{minargs}, @var{maxargs}, @var{nargs}) +## @deftypefnx {} {@var{msgstr} =} nargoutchk (@var{minargs}, @var{maxargs}, @var{nargs}, "string") +## @deftypefnx {} {@var{msgstruct} =} nargoutchk (@var{minargs}, @var{maxargs}, @var{nargs}, "struct") +## Check for correct number of output arguments. +## +## In the first form, return an error if the number of arguments is not between +## @var{minargs} and @var{maxargs}. Otherwise, do nothing. Note that this +## function evaluates the value of @code{nargout} on the caller so its value +## must have not been tampered with. +## +## Both @var{minargs} and @var{maxargs} must be numeric scalars. Zero, Inf, +## and negative are all valid, and they can have the same value. +## +## For backwards compatibility, the other forms return an appropriate error +## message string (or structure) if the number of outputs requested is +## invalid. +## +## This is useful for checking to that the number of output arguments supplied +## to a function is within an acceptable range. +## @seealso{narginchk, error, nargout, nargin} +## @end deftypefn + +## Author: Bill Denney <bill@denney.ws> +## Author: Carnë Draug <carandraug+dev@gmail.com> + +function msg = nargoutchk (minargs, maxargs, nargs, outtype) + + ## before matlab's 2011b, nargoutchk would return an error message (just the + ## message in a string). With 2011b, it no longer returns anything, it + ## simply gives an error if the args number is incorrect. + ## To try to keep compatibility with both versions, check nargout and nargin + ## to guess if the caller is expecting a value (old syntax) + ## or none (new syntax). + + if (nargout == 1 && (nargin == 3 || nargin == 4)) + + if (minargs > maxargs) + error ("nargoutchk: MINARGS must be <= MAXARGS"); + elseif (nargin == 3) + outtype = "string"; + elseif (! any (strcmpi (outtype, {"string" "struct"}))) + error ("nargoutchk: output type must be either string or struct"); + elseif (! (isscalar (minargs) && isscalar (maxargs) && isscalar (nargs))) + error ("nargoutchk: MINARGS, MAXARGS, and NARGS must be scalars"); + endif + + msg = struct ("message", "", "identifier", ""); + if (nargs < minargs) + msg.message = "not enough output arguments"; + msg.identifier = "Octave:nargoutchk:not-enough-outputs"; + elseif (nargs > maxargs) + msg.message = "too many output arguments"; + msg.identifier = "Octave:nargoutchk:too-many-outputs"; + endif + + if (strcmpi (outtype, "string")) + msg = msg.message; + elseif (isempty (msg.message)) + ## Compatibility: Matlab returns a 0x1 empty struct when nargoutchk passes + msg = resize (msg, 0, 1); + endif + + elseif (nargout == 0 && nargin == 2) + + if (! isnumeric (minargs) || ! isscalar (minargs)) + error ("nargoutchk: MINARGS must be a numeric scalar"); + elseif (! isnumeric (maxargs) || ! isscalar (maxargs)) + error ("nargoutchk: MAXARGS must be a numeric scalar"); + elseif (minargs > maxargs) + error ("nargoutchk: MINARGS cannot be larger than MAXARGS"); + endif + + args = evalin ("caller", "nargout;"); + + if (args < minargs) + error ("nargoutchk: Not enough output arguments."); + elseif (args > maxargs) + error ("nargoutchk: Too many output arguments."); + endif + + else + print_usage; + endif + +endfunction + + +%!shared stnul, stmin, stmax +%! stnul = resize (struct ("message", "", "identifier", ""), 0, 1); +%! stmin = struct ("message", "not enough output arguments", +%! "identifier", "Octave:nargoutchk:not-enough-outputs"); +%! stmax = struct ("message", "too many output arguments", +%! "identifier", "Octave:nargoutchk:too-many-outputs"); +%!assert (nargoutchk (0, 1, 0), "") +%!assert (nargoutchk (0, 1, 1), "") +%!assert (nargoutchk (1, 1, 0), "not enough output arguments") +%!assert (nargoutchk (0, 1, 2), "too many output arguments") +%!assert (nargoutchk (0, 1, 2, "string"), "too many output arguments") +## Struct outputs +%!assert (nargoutchk (0, 1, 0, "struct"), stnul) +%!assert (nargoutchk (0, 1, 1, "struct"), stnul) +%!assert (nargoutchk (1, 1, 0, "struct"), stmin) +%!assert (nargoutchk (0, 1, 2, "struct"), stmax)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/nthargout.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,116 @@ +## Copyright (C) 2012-2017 Jordi Gutiérrez Hermoso +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} nthargout (@var{n}, @var{func}, @dots{}) +## @deftypefnx {} {} nthargout (@var{n}, @var{ntot}, @var{func}, @dots{}) +## Return the @var{n}th output argument of the function specified by the +## function handle or string @var{func}. +## +## Any additional arguments are passed directly to @var{func}. The total +## number of arguments to call @var{func} with can be passed in @var{ntot}; by +## default @var{ntot} is @var{n}. The input @var{n} can also be a vector of +## indices of the output, in which case the output will be a cell array of the +## requested output arguments. +## +## The intended use @code{nthargout} is to avoid intermediate variables. For +## example, when finding the indices of the maximum entry of a matrix, the +## following two compositions of nthargout +## +## @example +## @group +## @var{m} = magic (5); +## cell2mat (nthargout ([1, 2], @@ind2sub, size (@var{m}), +## nthargout (2, @@max, @var{m}(:)))) +## @result{} 5 3 +## @end group +## @end example +## +## @noindent +## are completely equivalent to the following lines: +## +## @example +## @group +## @var{m} = magic (5); +## [~, idx] = max (@var{M}(:)); +## [i, j] = ind2sub (size (@var{m}), idx); +## [i, j] +## @result{} 5 3 +## @end group +## @end example +## +## It can also be helpful to have all output arguments in a single cell in the +## following manner: +## +## @example +## @var{USV} = nthargout ([1:3], @@svd, hilb (5)); +## @end example +## +## @seealso{nargin, nargout, varargin, varargout, isargout} +## @end deftypefn + +## Author: Jordi Gutiérrez Hermoso + +function out = nthargout (n, varargin) + + if (nargin < 2) + print_usage (); + endif + + if (isa (varargin{1}, "function_handle") || ischar (varargin{1})) + ntot = max (n(:)); + func = varargin{1}; + args = varargin(2:end); + elseif (isnumeric (varargin{1}) + && (isa (varargin{2}, "function_handle") || ischar (varargin{2}))) + ntot = varargin{1}; + func = varargin{2}; + args = varargin(3:end); + else + print_usage (); + endif + + if (any (n != fix (n)) || ntot != fix (ntot) || any (n <= 0) || ntot <= 0) + error ("nthargout: N and NTOT must consist of positive integers"); + endif + + outargs = cell (1, ntot); + + try + [outargs{:}] = feval (func, args{:}); + if (numel (n) > 1) + out = outargs(n); + else + out = outargs{n}; + endif + catch + err = lasterr (); + if (strfind ("some elements undefined in return list", err)) + error ("nthargout: Too many output arguments: %d", ntot); + else + error (err); + endif + end_try_catch + +endfunction + + +%!shared m +%! m = magic (5); +%!assert (nthargout ([1,2], @ind2sub, size (m), nthargout (2, @max, m(:))), {5,3}) +%!assert (nthargout (3, @find, m(m>20)), [23, 24, 25, 21, 22]')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/private/__publish_html_output__.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,430 @@ +## Copyright (C) 2016-2017 Kai T. Ohlhus +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {@var{outstr} =} __publish_html_output__ (@var{type}, @var{varargin}) +## +## Internal function. +## +## The first input argument @var{type} defines the required strings +## (@samp{str}) or cell-strings (@samp{cstr}) in @var{varargin} in order +## to produce HTML output. +## +## @var{type} is one of +## +## @itemize @bullet +## @item +## @samp{output_file_extension} () +## +## @item +## @samp{header} (title_str, intro_str, toc_cstr) +## +## @item +## @samp{footer} () +## +## @item +## @samp{code} (str) +## +## @item +## @samp{code_output} (str) +## +## @item +## @samp{section} (str) +## +## @item +## @samp{preformatted_code} (str) +## +## @item +## @samp{preformatted_text} (str) +## +## @item +## @samp{bulleted_list} (cstr) +## +## @item +## @samp{numbered_list} (cstr) +## +## @item +## @samp{graphic} (str) +## +## @item +## @samp{html} (str) +## +## @item +## @samp{latex} (str) +## +## @item +## @samp{text} (str) +## +## @item +## @samp{blockmath} (str) +## +## @item +## @samp{inlinemath} (str) +## +## @item +## @samp{bold} (str) +## +## @item +## @samp{italic} (str) +## +## @item +## @samp{monospaced} (str) +## +## @item +## @samp{link} (url_str, url_str, str) +## +## @item +## @samp{TM} () +## +## @item +## @samp{R} () +## +## @item +## @samp{escape_special_chars} (str) +## @end itemize +## @end deftypefn + +function outstr = __publish_html_output__ (type, varargin) + outstr = feval (["do_" type], varargin{:}); +endfunction + +function outstr = do_output_file_extension () + outstr = ".html"; +endfunction + +function outstr = do_header (title_str, intro_str, toc_cstr) + mathjax_str = sprintf ("%s\n", +'<script type="text/x-mathjax-config">', +"MathJax.Hub.Config({", +" tex2jax: { inlineMath: [['$','$'], ['\\\\(','\\\\)']] },", +" TeX: { equationNumbers: { autoNumber: 'all' } }", +"});", +"</script>", +['<script type="text/javascript" async ', ... + 'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?', ... + 'config=TeX-MML-AM_CHTML"></script>']); + + stylesheet_str = sprintf ("%s\n", +"<style>", +"body > * {", +" max-width: 42em;", +"}", +"body {", +' font-family: "Roboto Condensed", sans-serif;', +" padding-left: 7.5em;", +" padding-right: 7.5em;", +"}", +"pre, code {", +" max-width: 50em;", +" font-family: monospace;", +"}", +"pre.oct-code {", +" border: 1px solid Grey;", +" padding: 5px;", +"}", +"pre.oct-code-output {", +" margin-left: 2em;", +"}", +"span.comment {", +" color: ForestGreen;", +"}",... +"span.keyword {", +" color: Blue;", +"}",... +"span.string {", +" color: DarkOrchid;", +"}",... +"footer {", +" margin-top: 2em;", +" font-size: 80%;", +"}", +"a, a:visited {", +" color: Blue;", +"}", +"h2 {", +' font-family: "Roboto Condensed", serif;', +" margin-top: 1.5em;", +"}", +"h2 a, h2 a:visited {", +" color: Black;", +"}", +"</style>"); + + outstr = sprintf ("%s\n", +"<!DOCTYPE html>", +"<html>", +"<head>", +'<meta charset="UTF-8">', +["<title>" title_str "</title>"], +mathjax_str, +stylesheet_str, +"</head>", +"<body>", +["<h1>" title_str "</h1>"], +intro_str); + + if (! isempty (toc_cstr)) + for i = 1:numel (toc_cstr) + toc_cstr{i} = do_link (["#node" sprintf("%d", i)], toc_cstr{i}); + endfor + outstr = [outstr, "<h2>Contents</h2>", do_bulleted_list(toc_cstr)]; + endif + + ## Reset section counter + do_section (); + +endfunction + +function outstr = do_footer (m_source_str) + outstr = sprintf ("%s\n", +"", +"<footer>", +"<hr>", +['<a href="http://www.octave.org">Published with GNU Octave ' version() '</a>'], +"</footer>", +"<!--", +"##### SOURCE BEGIN #####", +m_source_str, +"##### SOURCE END #####", +"-->", +"</body>", +"</html>"); +endfunction + +function outstr = do_code (str) + outstr = ["\n", '<pre class="oct-code">' syntax_highlight(str) "</pre>\n"]; +endfunction + +function outstr = do_code_output (str) + str = do_escape_special_chars (str); + outstr = ["\n", '<pre class="oct-code-output">' str "</pre>\n"]; +endfunction + +function outstr = do_section (varargin) + persistent counter = 1; + + if (nargin == 0) + outstr = ""; + counter = 1; + return; + endif + + outstr = ['<h2><a id="node' sprintf("%d", counter) '">', ... + varargin{1}, ... + "</a></h2>"]; + + counter++; + +endfunction + +function outstr = do_preformatted_code (str) + outstr = ["\n", '<pre class="pre-code">' syntax_highlight(str) "</pre>\n"]; +endfunction + +function outstr = do_preformatted_text (str) + str = do_escape_special_chars (str); + outstr = ["\n", '<pre class="pre-text">' str "</pre>\n"]; +endfunction + +function outstr = do_bulleted_list (cstr) + outstr = "\n<ul>\n"; + for i = 1:numel (cstr) + outstr = [outstr, "<li>" cstr{i} "</li>\n"]; + endfor + outstr = [outstr, "</ul>\n"]; +endfunction + +function outstr = do_numbered_list (cstr) + outstr = "\n<ol>\n"; + for i = 1:numel (cstr) + outstr = [outstr, "<li>" cstr{i} "</li>\n"]; + endfor + outstr = [outstr, "</ol>\n"]; +endfunction + +function outstr = do_graphic (str) + outstr = ['<img src="' str '" alt="' str '">']; +endfunction + +function outstr = do_html (str) + outstr = ["\n" str "\n"]; +endfunction + +function outstr = do_latex (str) + outstr = ""; +endfunction + +function outstr = do_link (url_str, str) + outstr = ['<a href="' url_str '">' str "</a>"]; +endfunction + +function outstr = do_text (str) + outstr = ["\n<p>" str "</p>\n"]; +endfunction + +function outstr = do_blockmath (str) + outstr = ["$$" str "$$"]; +endfunction + +function outstr = do_inlinemath (str) + outstr = ["$" str "$"]; +endfunction + +function outstr = do_bold (str) + outstr = ["<b>" str "</b>"]; +endfunction + +function outstr = do_italic (str) + outstr = ["<i>" str "</i>"]; +endfunction + +function outstr = do_monospaced (str) + outstr = ["<code>" str "</code>"]; +endfunction + +function outstr = do_TM () + outstr = "™"; +endfunction + +function outstr = do_R () + outstr = "®"; +endfunction + +function str = do_escape_special_chars (str) + str = regexprep (str, '&', '&'); + str = regexprep (str, '<', '<'); + str = regexprep (str, '>', '>'); + ## str = regexprep (str, '"', '"'); ## MATLAB R2017a compatibility. +endfunction + +## SYNTAX_HIGHLIGHT: A primitive parser to highlight syntax via <span> tags. +## FIXME: Needs to be replaced by a better solution. +function outstr = syntax_highlight (str) + str = do_escape_special_chars (str); + outstr = ""; + placeholder_cstr = {}; + i = 1; + plh = 0; + + while (i <= numel (str)) + ## Block comment + if (any (strncmp (str(i:end), {"%{", "#{"}, 2))) + plh_str = ['<span class="comment">', str(i:i+1)]; + i += 2; + while (i <= numel (str) + && ! (any (strncmp (str(i:end), {"%}", "#}"}, 2)))) + plh_str = [plh_str, str(i)]; + i += 1; + endwhile + if (i < numel (str)) + plh_str = [plh_str, str(i:i+1), "</span>"]; + i += 2; + else + plh_str = [plh_str, "</span>"]; + endif + plh += 1; + placeholder_cstr{plh} = plh_str; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; + ## Line comment + elseif (str(i) == "#" || str(i) == "%") + plh_str = '<span class="comment">'; + idx = find (str(i:end) == "\n", 1); + if (isempty (idx)) + plh_str = [plh_str, str(i:end)]; + i = numel (str) + 1; + else + plh_str = [plh_str, str(i:i+idx-2)]; + i += idx; + endif + plh_str = [plh_str, "</span>\n"]; + plh += 1; + placeholder_cstr{plh} = plh_str; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; + ## Single quoted string + elseif (str(i) == "'") + plh_str = "<span class=\"string\">'"; + i += 1; + while (i <= numel (str)) + ## Ignore escaped string terminations + if (strncmp (str(i:end), "''", 2)) + plh_str = [plh_str, "''"]; + i += 2; + ## Is char a string termination? + elseif (str(i) == "'") + plh_str = [plh_str, "'"]; + i += 1; + break; + ## Is string terminated by line break? + elseif (str(i) == "\n") + break; + ## String content + else + plh_str = [plh_str, str(i)]; + i += 1; + endif + endwhile + plh_str = [plh_str, "</span>"]; + plh += 1; + placeholder_cstr{plh} = plh_str; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; + ## Double quoted string + elseif (str(i) == '"') + plh_str = '<span class="string">"'; + i += 1; + while (i <= numel (str)) + ## Is char a string termination? + if (str(i) == '"' && str(i-1) != '\') + plh_str = [plh_str, '"']; + i += 1; + break; + ## Is string terminated by line break? + elseif (str(i) == "\n") + break; + ## String content + else + plh_str = [plh_str, str(i)]; + i += 1; + endif + endwhile + plh_str = [plh_str, "</span>"]; + plh += 1; + placeholder_cstr{plh} = plh_str; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; + else + outstr = [outstr, str(i)]; + i += 1; + endif + endwhile + + persistent kword_ptn = strjoin (iskeyword (), '|'); + + ## FIXME: remove hack for regexprep once bug #38149 is solved + outstr = [" ", strrep(outstr, "\n", " \n "), " "]; + outstr = regexprep (outstr, + ['(\s)(' kword_ptn ')(\s|\()'], + ['$1<span class="keyword">$2</span>$3']); + ## FIXME: remove hack for regexprep once bug #38149 is solved + outstr = strrep (outstr(2:end-1), " \n ", "\n"); + + ## Restore placeholders + for i = plh:-1:1 + outstr = strrep (outstr, [" PUBLISHPLACEHOLDER", sprintf("%d", i), " "], + placeholder_cstr{i}); + endfor + +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/private/__publish_latex_output__.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,280 @@ +## Copyright (C) 2016-2017 Kai T. Ohlhus +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {@var{outstr} =} __publish_latex_output__ (@var{type}, @var{varargin}) +## +## Internal function. +## +## The first input argument @var{type} defines the required strings +## (@samp{str}) or cell-strings (@samp{cstr}) in @var{varargin} in order +## to produce @LaTeX{} output. +## +## @var{type} is one of +## +## @itemize @bullet +## @item +## @samp{output_file_extension} () +## +## @item +## @samp{header} (title_str, intro_str, toc_cstr) +## +## @item +## @samp{footer} () +## +## @item +## @samp{code} (str) +## +## @item +## @samp{code_output} (str) +## +## @item +## @samp{section} (str) +## +## @item +## @samp{preformatted_code} (str) +## +## @item +## @samp{preformatted_text} (str) +## +## @item +## @samp{bulleted_list} (cstr) +## +## @item +## @samp{numbered_list} (cstr) +## +## @item +## @samp{graphic} (str) +## +## @item +## @samp{html} (str) +## +## @item +## @samp{latex} (str) +## +## @item +## @samp{text} (str) +## +## @item +## @samp{blockmath} (str) +## +## @item +## @samp{inlinemath} (str) +## +## @item +## @samp{bold} (str) +## +## @item +## @samp{italic} (str) +## +## @item +## @samp{monospaced} (str) +## +## @item +## @samp{link} (url_str, url_str, str) +## +## @item +## @samp{TM} () +## +## @item +## @samp{R} () +## +## @item +## @samp{escape_special_chars} (str) +## @end itemize +## @end deftypefn + +function outstr = __publish_latex_output__ (type, varargin) + outstr = feval (["do_" type], varargin{:}); +endfunction + +function outstr = do_output_file_extension () + outstr = ".tex"; +endfunction + +function outstr = do_header (title_str, intro_str, toc_cstr) + publish_comment = sprintf ("%s\n", +"", +"", +"% This document was generated by the publish-function", +["% from GNU Octave " version()], +""); + + latex_preamble = sprintf ("%s\n", +"", +"", +'\documentclass[10pt]{article}', +'\usepackage{listings}', +'\usepackage{mathtools}', +'\usepackage{amssymb}', +'\usepackage{graphicx}', +'\usepackage{hyperref}', +'\usepackage{xcolor}', +'\usepackage{titlesec}', +'\usepackage[utf8]{inputenc}', +'\usepackage[T1]{fontenc}', +'\usepackage{lmodern}'); + + listings_option = sprintf ("%s\n", +"", +"", +'\lstset{', +'language=Octave,', +'numbers=none,', +'frame=single,', +'tabsize=2,', +'showstringspaces=false,', +'breaklines=true}'); + + latex_head = sprintf ("%s\n", +"", +"", +'\titleformat*{\section}{\Huge\bfseries}', +'\titleformat*{\subsection}{\large\bfseries}', +'\renewcommand{\contentsname}{\Large\bfseries Contents}', +'\setlength{\parindent}{0pt}', +"", +'\begin{document}', +"", +['{\Huge\section*{' title_str '}}'], +"", +'\tableofcontents', +'\vspace*{4em}', +""); + + outstr = [publish_comment, latex_preamble, listings_option, latex_head]; + +endfunction + +function outstr = do_footer (m_source_str) + outstr = ["\n\n" '\end{document}' "\n"]; +endfunction + +function outstr = do_code (str) + outstr = ['\begin{lstlisting}' "\n", str, "\n" '\end{lstlisting}' "\n"]; +endfunction + +function outstr = do_code_output (str) + outstr = sprintf ("%s\n", +'\begin{lstlisting}[language={},xleftmargin=5pt,frame=none]', +str, +'\end{lstlisting}'); +endfunction + +function outstr = do_section (str) + outstr = sprintf ("%s\n", +"", +"", +'\phantomsection', +['\addcontentsline{toc}{section}{' str '}'], +['\subsection*{' str '}'], +""); +endfunction + +function outstr = do_preformatted_code (str) + outstr = sprintf ("%s\n", +'\begin{lstlisting}', +str, +'\end{lstlisting}'); +endfunction + +function outstr = do_preformatted_text (str) + outstr = sprintf ("%s\n", +'\begin{lstlisting}[language={}]', +str, +'\end{lstlisting}'); +endfunction + +function outstr = do_bulleted_list (cstr) + outstr = ["\n" '\begin{itemize}' "\n"]; + for i = 1:numel (cstr) + outstr = [outstr, '\item ' cstr{i} "\n"]; + endfor + outstr = [outstr, '\end{itemize}' "\n"]; +endfunction + +function outstr = do_numbered_list (cstr) + outstr = ["\n" '\begin{enumerate}' "\n"]; + for i = 1:numel (cstr) + outstr = [outstr, '\item ' cstr{i} "\n"]; + endfor + outstr = [outstr, "\\end{enumerate}\n"]; +endfunction + +function outstr = do_graphic (str) + outstr = sprintf ("%s\n", +'\begin{figure}[!ht]', +['\includegraphics[width=\textwidth]{' str '}'], +'\end{figure}'); +endfunction + +function outstr = do_html (str) + outstr = ""; +endfunction + +function outstr = do_latex (str) + outstr = ["\n" str "\n"]; +endfunction + +function outstr = do_link (url_str, str) + outstr = ['\href{' url_str '}{' str '}']; +endfunction + +function outstr = do_text (str) + outstr = ["\n\n" str "\n\n"]; +endfunction + +function outstr = do_blockmath (str) + outstr = ["$$" str "$$"]; +endfunction + +function outstr = do_inlinemath (str) + outstr = ["$" str "$"]; +endfunction + +function outstr = do_bold (str) + outstr = ['\textbf{' str '}']; +endfunction + +function outstr = do_italic (str) + outstr = ['\textit{' str '}']; +endfunction + +function outstr = do_monospaced (str) + outstr = ['\texttt{' str '}']; +endfunction + +function outstr = do_TM () + outstr = '\texttrademark '; +endfunction + +function outstr = do_R () + outstr = '\textregistered '; +endfunction + +function str = do_escape_special_chars (str) + ## Escape \, {, }, &, %, #, _, ~, ^, <, > + str = regexprep (str, '\\', "\\ensuremath{\\backslash}"); + str = regexprep (str, '(?<!\\)(\{|\}|&|%|#|_)', '\\$1'); + ## Revert accidential {} replacements for backslashes + str = strrep (str, '\ensuremath\{\backslash\}', '\ensuremath{\backslash}'); + str = regexprep (str, '(?<!\\)~', "\\ensuremath{\\tilde{\\;}}"); + str = regexprep (str, '(?<!\\)\^', "\\^{}"); + str = regexprep (str, '(?<!\\)<', "\\ensuremath{<}"); + str = regexprep (str, '(?<!\\)>', "\\ensuremath{>}"); +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/publish.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,1071 @@ +## Copyright (C) 2016-2017 Kai T. Ohlhus <k.ohlhus@gmail.com> +## Copyright (C) 2010 Fotios Kasolis <fotios.kasolis@gmail.com> +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} publish (@var{file}) +## @deftypefnx {} {} publish (@var{file}, @var{output_format}) +## @deftypefnx {} {} publish (@var{file}, @var{option1}, @var{value1}, @dots{}) +## @deftypefnx {} {} publish (@var{file}, @var{options}) +## @deftypefnx {} {@var{output_file} =} publish (@var{file}, @dots{}) +## +## Generate a report from the Octave script file @var{file} in one of several +## output formats. +## +## The generated reports interpret any Publishing Markup in comments, which is +## explained in detail in the GNU Octave manual. Assume the following example, +## using some Publishing Markup, to be the contents of the script file +## @file{pub_example.m}: +## +## @example +## @group +## ## Headline title +## # +## # Some *bold*, _italic_, or |monospaced| Text with +## # a <http://www.octave.org link to *GNU Octave*>. +## ## +## +## # "Real" Octave commands to be evaluated +## sombrero () +## +## %% @sc{matlab} comment style ('%') is supported as well +## % +## % * Bulleted list item 1 +## % * Bulleted list item 2 +## % +## % # Numbered list item 1 +## % # Numbered list item 2 +## @end group +## @end example +## +## To publish this script file, type @code{publish ("pub_example.m")}. +## +## With only @var{file} given, a HTML report is generated in a subdirectory +## @file{html} relative to the current working directory. The Octave commands +## are evaluated in a separate context and any figures created while executing +## the script file are included in the report. All formatting syntax of +## @var{file} is treated according to the specified output format and included +## in the report. +## +## Using @code{publish (@var{file}, @var{output_format})} is equivalent to the +## function call using a structure +## +## @example +## @group +## @var{options}.format = @var{output_format}; +## publish (@var{file}, @var{options}) +## @end group +## @end example +## +## @noindent +## which is described below. The same holds for using option/value pairs +## +## @example +## @group +## @var{options}.@var{option1} = @var{value1}; +## publish (@var{file}, @var{options}) +## @end group +## @end example +## +## The structure @var{options} can have the following field names. If a field +## name is not specified, the default value is used: +## +## @itemize @bullet +## @item +## @samp{format} --- Output format of the published script file, one of +## +## @samp{html} (default), @samp{doc}, @samp{latex}, @samp{ppt}, +## @samp{pdf}, or @samp{xml}. +## +## The output formats @samp{doc}, @samp{ppt}, and @samp{xml} are not currently +## supported. To generate a @samp{doc} report, open a generated @samp{html} +## report with your office suite. +## +## In Octave custom formats are supported by implementing all callback +## subfunctions in a function file named +## @samp{__publish_<custom format>_output__.m}. To obtain a template for the +## HTML format type: +## +## @example +## @group +## edit (fullfile (fileparts (which ("publish")), ... +## "private", "__publish_html_output__.m")) +## @end group +## @end example +## +## @item +## @samp{outputDir} --- Full path of the directory where the generated report +## will be located. If no directory is given, the report is generated in a +## subdirectory @file{html} relative to the current working directory. +## +## @item +## @samp{stylesheet} --- Not supported, only for @sc{matlab} compatibility. +## +## @item +## @samp{createThumbnail} --- Not supported, only for @sc{matlab} +## compatibility. +## +## @item +## @samp{figureSnapMethod} --- Not supported, only for @sc{matlab} +## compatibility. +## +## @item +## @samp{imageFormat} --- Desired format for any images produced while +## evaluating the code. The allowed image formats depend on the output format: +## +## @itemize @bullet +## @item @samp{html}, @samp{xml} --- @samp{png} (default), any image format +## supported by Octave +## +## @item @samp{latex} --- @samp{epsc2} (default), any image format supported by +## Octave +## +## @item @samp{pdf} --- @samp{jpg} (default) or @samp{bmp}, note @sc{matlab} +## uses @samp{bmp} as default +## +## @item @samp{doc} or @samp{ppt} --- @samp{png} (default), @samp{jpg}, +## @samp{bmp}, or @samp{tiff} +## @end itemize +## +## @item +## @samp{maxWidth} and @samp{maxHeight} --- Maximum width (height) of the +## produced images in pixels. An empty value means no restriction. Both +## values must be set in order for the option to work properly. +## +## @samp{[]} (default), integer value @geq{} 0 +## +## @item +## @samp{useNewFigure} --- Use a new figure window for figures created by the +## evaluated code. This avoids side effects with already opened figure +## windows. +## +## @samp{true} (default) or @samp{false} +## +## @item +## @samp{evalCode} --- Evaluate code of the Octave source file +## +## @samp{true} (default) or @samp{false} +## +## @item +## @samp{catchError} --- Catch errors while evaluating code and continue +## +## @samp{true} (default) or @samp{false} +## +## @item +## @samp{codeToEvaluate} --- Octave commands that should be evaluated prior to +## publishing the script file. These Octave commands do not appear in the +## generated report. +## +## @item +## @samp{maxOutputLines} --- Maximum number of output lines from code +## evaluation which are included in output. +## +## @samp{Inf} (default) or integer value > 0 +## +## @item +## @samp{showCode} --- Show the evaluated Octave commands in the generated +## report +## +## @samp{true} (default) or @samp{false} +## @end itemize +## +## The option output @var{output_file} is a string with path and file name +## of the generated report. +## +## @seealso{grabcode} +## @end deftypefn + +function output_file = publish (file, varargin) + + if (nargin < 1) + print_usage (); + endif + + if (exist (file, "file") != 2) + error ("publish: FILE does not exist"); + endif + + ## Check file to be in Octave's load path + [file_path, file_name, file_ext] = fileparts (file); + if (isempty (file_path)) + file_path = pwd; + endif + if (exist ([file_name, file_ext]) != 2) + error (["publish: " file " is not in the load path"]); + endif + + ## Check file extension and that file is an Octave script + file_info = __which__ (file_name); + if (! strcmp (file_ext, ".m") || ! strcmp (file_info.type, "script")) + error ("publish: only script files can be published"); + endif + + ## Check file to be parsable + __parse_file__ (file); + + ## Get structure with necessary options + options = struct (); + if (numel (varargin) == 1) + ## Call: publish (file, format) + if (ischar (varargin{1})) + options.format = varargin{1}; + ## Call: publish (file, options) + elseif (isstruct (varargin{1})) + options = varargin{1}; + else + error ("publish: second argument must be OUTPUT_FORMAT or OPTIONS"); + endif + ## Call: publish (file, Name1, Value1, Name2, Value2, ...) + elseif (rem (numel (varargin), 2) == 0 + && all (cellfun (@ischar, varargin(1:2:end)))) + options = cell2struct (varargin(2:2:end), varargin(1:2:end), 2); + else + error ("publish: invalid arguments"); + endif + + ## Validate options struct + + ## Options for the output + if (! isfield (options, "format")) + options.format = "html"; + else + ## FIXME: Implement remaining formats + if (any (strcmpi (options.format, {"doc", "ppt", "xml"}))) + error ('publish: Output format "%s" is not yet supported', + options.format); + endif + ## Supported or custom output format + supported_formats = {"html", "doc", "latex", "ppt", "xml", "pdf"}; + if (! any (strcmpi (options.format, supported_formats))) + ## Check existence of custom formatter + custom_formatter = ["__publish_", options.format, "_output__"]; + if (! exist (custom_formatter, "file")) + error (['publish: Custom output format "%s" requires the ', ... + "formatter function:\n\n\t%s\n\n\t", ... + 'See "help publish" for more information.'], + options.format, custom_formatter); + endif + else + options.format = validatestring (options.format, supported_formats); + endif + endif + + if (! isfield (options, "outputDir")) + ## Matlab R2016a doc says default is "", but specifies to create a + ## subdirectory named "html" in the current working directory. + options.outputDir = fullfile (file_path, "html"); + elseif (! ischar (options.outputDir)) + error ("publish: OUTPUTDIR must be a string"); + endif + + if (! isfield (options, "stylesheet")) + options.stylesheet = ""; + elseif (! ischar (options.stylesheet)) + error ("publish: STYLESHEET must be a string"); + endif + + ## Options for the figures + if (! isfield (options, "createThumbnail")) + options.createThumbnail = true; + elseif (! isscalar (options.createThumbnail) + || ! isreal (options.createThumbnail)) + error ("publish: CREATETHUMBNAIL must be TRUE or FALSE"); + endif + + if (! isfield (options, "figureSnapMethod")) + options.figureSnapMethod = "entireGUIWindow"; + else + options.figureSnapMethod = validatestring (options.figureSnapMethod, ... + {"entireGUIWindow", "print", "getframe", "entireFigureWindow"}); + ## FIXME: implement other SnapMethods + warning ("publish: option FIGURESNAPMETHOD currently not supported"); + endif + + if (! isfield (options, "imageFormat")) + switch (options.format) + case "latex" + options.imageFormat = "epsc2"; + case "pdf" + ## Note: Matlab R2016a uses bmp as default + options.imageFormat = "jpg"; + otherwise + options.imageFormat = "png"; + endswitch + elseif (! ischar (options.imageFormat)) + error ("publish: IMAGEFORMAT must be a string"); + else + ## Check valid imageFormat for chosen format + ## html, latex, and xml accept any imageFormat + switch (options.format) + case {"doc", "ppt"} + options.imageFormat = validatestring (options.imageFormat, + {"png", "jpg", "bmp", "tiff"}); + case "pdf" + options.imageFormat = validatestring (options.imageFormat, + {"bmp", "jpg"}); + endswitch + endif + + if (! isfield (options, "maxHeight")) + options.maxHeight = []; + elseif (! isscalar (options.maxHeight) || options.maxHeight < 1) + error ("publish: MAXHEIGHT must be a positive integer"); + else + options.maxHeight = uint64 (options.maxHeight); + endif + + if (! isfield (options, "maxWidth")) + options.maxWidth = []; + elseif (! isscalar (options.maxWidth) || options.maxWidth < 1) + error ("publish: MAXWIDTH must be a positive integer"); + else + options.maxWidth = uint64 (options.maxWidth); + endif + + if (! isfield (options, "useNewFigure")) + options.useNewFigure = true; + elseif (! isscalar (options.useNewFigure) || ! isreal (options.useNewFigure)) + error ("publish: USENEWFIGURE must be TRUE or FALSE"); + endif + + ## Options for the code + if (! isfield (options, "evalCode")) + options.evalCode = true; + elseif (! isscalar (options.evalCode) || ! isreal (options.evalCode)) + error ("publish: EVALCODE must be TRUE or FALSE"); + endif + + if (! isfield (options, "catchError")) + options.catchError = true; + elseif (! isscalar (options.catchError) || ! isreal (options.catchError)) + error ("publish: CATCHERROR must be TRUE or FALSE"); + endif + + if (! isfield (options, "codeToEvaluate")) + options.codeToEvaluate = ""; + elseif (! ischar (options.codeToEvaluate)) + error ("publish: CODETOEVALUTE must be a string"); + endif + + if (! isfield (options, "maxOutputLines")) + options.maxOutputLines = Inf; + elseif (! isscalar (options.maxOutputLines) || options.maxOutputLines < 0) + error ("publish: MAXOUTPUTLINES must be an integer >= 0"); + else + options.maxOutputLines = uint64 (options.maxOutputLines); + endif + + if (! isfield (options, "showCode")) + options.showCode = true; + elseif (! isscalar (options.showCode) || ! isreal (options.showCode)) + error ("publish: SHOWCODE must be TRUE or FALSE"); + endif + + doc.title = ""; + doc.intro = ""; + doc.body = cell (); + doc.m_source = deblank (read_file_to_cellstr (file)); + doc.m_source_file_name = file; + + ## Split code and paragraphs, find formatting + doc = parse_m_source (doc); + + ## Create output directory + [status, msg] = mkdir (options.outputDir); + if (status != 1) + error ("publish: cannot create output directory: %s", msg); + endif + + if (options.evalCode) + doc = eval_code (doc, options); + endif + + output_file = create_output (doc, options); + +endfunction + + +function doc = parse_m_source (doc) + ## PARSE_M_SOURCE First parsing level + ## This function extracts the overall structure (paragraphs and code + ## sections) given in doc.m_source. + ## + ## The result is written to doc.body, which then contains a cell + ## vector of structs, either of + ## + ## a) {struct ("type", "code", ... + ## "lines", [a, b], ... + ## "output", [])} + ## b) {struct ("type", "section", ... + ## "content", title_str)} + ## + ## Second parsing level is invoked for the paragraph contents, resulting + ## in more elements for doc.body. + + if (isempty (doc.m_source)) + return; # Nothing to parse + endif + + ## Parsing helper functions + ## + ## Checks line to have N "%" or "#" lines + ## followed either by a space or end of string + is_publish_markup = @(cstr, N) ... + any (strncmp (char (cstr), {"%%%", "##"}, N)) ... + && ((length (char (cstr)) == N) || ((char (cstr))(N + 1) == " ")); + ## Checks line of cellstring to be a paragraph line + is_paragraph = @(cstr) is_publish_markup (cstr, 1); + ## Checks line of cellstring to be a section headline + is_head = @(cstr) is_publish_markup (cstr, 2); + ## Checks line of cellstring to be a headline without section break, using + ## the cell mode in Matlab (for compatibility), just treated as a new head. + is_no_break_head = @(cstr) is_publish_markup (cstr, 3); + + ## Find the indices of paragraphs starting with "%%", "##", or "%%%" + par_start_idx = find (cellfun (is_head, doc.m_source) + | cellfun (is_no_break_head, doc.m_source)); + + ## If the whole document is code + if (isempty (par_start_idx)) + doc.body{end+1}.type = "code"; + doc.body{end}.content = strtrim (strjoin (doc.m_source, "\n")); + doc.body{end}.lines = [1, length(doc.m_source)]; + doc.body{end}.output = {}; + return; + endif + + ## Determine continuous range of paragraphs + par_end_idx = [par_start_idx(2:end) - 1, length(doc.m_source)]; + for i = 1:numel (par_end_idx) + idx = find (! cellfun (is_paragraph, + doc.m_source(par_start_idx(i) + 1:par_end_idx(i)))); + if (! isempty (idx)) + par_end_idx(i) = par_start_idx(i) + idx(1) - 1; + endif + endfor + ## Code sections between paragraphs + code_start_idx = par_end_idx(1:end-1) + 1; + code_end_idx = par_start_idx(2:end) - 1; + ## Code at the beginning? + if (par_start_idx(1) > 1) + code_start_idx = [1, code_start_idx]; + code_end_idx = [par_start_idx(1) - 1, code_end_idx]; + endif + ## Code at the end? + if (par_end_idx(end) < length (doc.m_source)) + code_start_idx = [code_start_idx, par_end_idx(end) + 1]; + code_end_idx = [code_end_idx, length(doc.m_source)]; + endif + ## Remove overlaps + idx = code_start_idx > code_end_idx; + code_start_idx(idx) = []; + code_end_idx(idx) = []; + ## Remove empty code blocks + idx = []; + for i = 1:numel (code_start_idx) + if (all (cellfun (@(cstr) isempty (char (cstr)), + doc.m_source(code_start_idx(i):code_end_idx(i))))) + idx = [idx, i]; + endif + endfor + code_start_idx(idx) = []; + code_end_idx(idx) = []; + + ## Try to find a document title and introduction text + ## 1. First paragraph must start in first line + ## 2. Second paragraph must start before any code + title_offset = 0; + if (is_head (doc.m_source{1}) + && ! isempty (par_start_idx) + && par_start_idx(1) == 1 + && (isempty (code_start_idx) + || (length (par_start_idx) > 1 + && par_start_idx(2) < code_start_idx(1)))) + doc.title = doc.m_source{1}; + doc.title = doc.title(4:end); + content = doc.m_source(2:par_end_idx(1)); + ## Strip leading "# " + content = cellfun (@(c) cellstr (c(3:end)), content); + doc.intro = parse_paragraph_content (content); + title_offset = 1; + endif + + ## Add non-empty paragraphs and code to doc + j = 1; + i = (1 + title_offset); + while (i <= numel (par_start_idx) || j <= numel (code_start_idx)) + ## Add code while there is code left + ## and code is before the next paragraph or there are no more paragraphs + while (j <= numel (code_start_idx) + && (i > numel (par_start_idx) + || par_start_idx(i) > code_start_idx(j))) + doc.body{end+1}.type = "code"; + lines = [code_start_idx(j), code_end_idx(j)]; + doc.body{end}.content = ... + strtrim (strjoin (doc.m_source(lines(1):lines(2)), "\n")); + doc.body{end}.lines = lines; + doc.body{end}.output = {}; + j++; + endwhile + + if (i <= numel (par_start_idx)) + type_str = "section"; + title_str = doc.m_source{par_start_idx(i)}; + if (is_head (doc.m_source(par_start_idx(i)))) + title_str = title_str(4:end); + else + title_str = title_str(5:end); + endif + ## Append, if paragraph title is given + if (! isempty (title_str)) + doc.body{end+1}.type = type_str; + doc.body{end}.content = title_str; + endif + + content = doc.m_source(par_start_idx(i) + 1:par_end_idx(i)); + ## Strip leading "# " + content = cellfun (@(c) cellstr (c(3:end)), content); + doc.body = [doc.body, parse_paragraph_content(content)]; + i++; + endif + endwhile + +endfunction + + +function p_content = parse_paragraph_content (content) + ## PARSE_PARAGRAPH_CONTENT second parsing level + ## + ## Parses the content of a paragraph (without potential title) and + ## returns a cell vector of structs, that can be appended to doc.body, + ## either of + ## + ## a) {struct ("type", "preformatted_code", ... + ## "content", code_str)} + ## b) {struct ("type", "preformatted_text", ... + ## "content", text_str)} + ## c) {struct ("type", "bulleted_list", ... + ## "content", {"item1", "item2", ..})} + ## d) {struct ("type", "numbered_list", ... + ## "content", {"item1", "item2", ..})} + ## e) {struct ("type", "include", ... + ## "content", file_str)} + ## f) {struct ("type", "graphic", ... + ## "content", file_str)} + ## g) {struct ("type", "html", ... + ## "content", html_str)} + ## h) {struct ("type", "latex", ... + ## "content", latex_str)} + ## i) {struct ("type", "text", ... + ## "content", text_str)} + ## + ## Option i) might contain: + ## + ## * Italic "_", bold "*", and monospaced "|" text + ## * Inline "$" and block "$$" LaTeX math + ## * Links + ## * Trademark symbols + + p_content = cell (); + + if (isempty (content)) + return; + endif + + ## Extract <html> and <latex> blocks recursively. + content_str = strjoin (content, "\n"); + tags = {"html", "latex"}; + for i = 1:length(tags) + tok = regexp (content_str, ... + ['(.*?)(^|\n\n)(<', tags{i}, '>)\n(.*?)\n(<\/', ... + tags{i}, '>)($|\n\n)(.*)'], "tokens", "once"); + if (! isempty (tok)) + ## If there was some text before that block --> recursion + if (! strcmpi (tok{1}, ["<", tags{i}, ">"])) + p_content = parse_paragraph_content (strsplit (tok{1}, "\n")); + tok(1:2) = []; + endif + ## Extract the block content + p_content{end+1}.type = tags{i}; + p_content{end}.content = tok{2}; + ## If there was some text after that block --> recursion + if (length (tok) == 5) + p_content = [p_content, ... + parse_paragraph_content(strsplit (tok{5}, "\n"))]; + endif + return; + endif + endfor + + ## Split into blocks separated by empty lines + idx = [0, find(cellfun (@isempty, content)), length(content) + 1]; + + ## For each block + for i = find (diff (idx) > 1) + block = content(idx(i) + 1:idx(i+1) - 1); + + ## Octave code (two leading spaces) + if (all (cellfun (@(c) strncmp (char (c), " ", 2), block))) + p_content{end+1}.type = "preformatted_code"; + block = cellfun (@(c) cellstr (c(3:end)), block); + p_content{end}.content = strjoin (block, "\n"); + continue; + endif + + ## Preformatted text (one leading space) + if (all (cellfun (@(c) strncmp (char (c), " ", 1), block))) + p_content{end+1}.type = "preformatted_text"; + block = cellfun (@(c) cellstr (c(2:end)), block); + p_content{end}.content = strjoin (block, "\n"); + continue; + endif + + ## Bulleted list starts with "* " + if (strncmp (block{1}, "* ", 2)) + p_content{end+1}.type = "bulleted_list"; + tmpstr = strjoin (block, "\n"); + ## Remove first "* " + tmpstr = tmpstr(3:end); + ## Split items + p_content{end}.content = strsplit (tmpstr, "\n* "); + continue; + endif + + ## Numbered list starts with "# " + if (strncmp (block{1}, "# ", 2)) + p_content{end+1}.type = "numbered_list"; + tmpstr = strjoin (block, "\n"); + ## Remove first "# " + tmpstr = tmpstr(3:end); + ## Split items + p_content{end}.content = strsplit (tmpstr, "\n# "); + continue; + endif + + ## Include <include>fname.m</include> + if (! isempty (fname = regexpi (strjoin (block, ""), + '^<include>(.*)</include>$', + "tokens"))) + ## Includes result in preformatted code + p_content{end+1}.type = "preformatted_code"; + include_code = read_file_to_cellstr (strtrim ((fname{1}){1})); + p_content{end}.content = strjoin (include_code, "\n"); + + continue; + endif + + ## Graphic <<myGraphic.png>> + if (! isempty (fname = regexpi (strjoin (block, ""), + '^<<(.*)>>$', + "tokens"))) + p_content{end+1}.type = "graphic"; + p_content{end}.content = strtrim ((fname{1}){1}); + continue; + endif + + ## Now it can be only normal text or markups belonging to normal text + ## that are handled while output generation: + ## + ## * Italic "_", bold "*", and monospaced "|" text + ## * Inline "$" and block "$$" LaTeX math + ## * Links + ## * Trademark symbols + p_content{end+1}.type = "text"; + p_content{end}.content = strjoin (block, "\n"); + endfor +endfunction + + +function m_source = read_file_to_cellstr (file) + ## READ_FILE_TO_CELLSTR reads a given file line by line into a cellstring + + fid = fopen (file, "r"); + i = 0; + do + m_source{++i} = fgetl (fid); + until (! ischar (m_source{i})) + fclose (fid); + m_source = m_source(1:end-1); # No EOL +endfunction + + +function ofile = create_output (doc, options) + ## CREATE_OUTPUT creates the desired output file + + formatter = []; + switch (options.format) + case "html" + formatter = @__publish_html_output__; + case {"latex", "pdf"} + formatter = @__publish_latex_output__; + otherwise + ## Custom formatter + formatter = eval (["@__publish_", options.format, "_output__"]); + endswitch + + ## Use title, or if not given, the m-file name + title_str = doc.title; + if (isempty (title_str)) + [~, title_str] = fileparts (doc.m_source_file_name); + endif + + content = formatter ("header", + formatter ("escape_special_chars", title_str), + format_output (doc.intro, formatter, options), + get_toc (doc.body, formatter)); + content = [content, format_output(doc.body, formatter, options)]; + content = [content, formatter("footer", strjoin (doc.m_source, "\n"))]; + + ## Write file + [~, ofile] = fileparts (doc.m_source_file_name); + ofile_name = [ofile, formatter("output_file_extension")]; + ofile = fullfile (options.outputDir, ofile_name); + fid = fopen (ofile, "w"); + fputs (fid, content); + fclose (fid); + + ## Compile LaTeX, if compiler found + if (strcmp (options.format, "pdf")) + status = system ("pdflatex --version"); + if (status == 0) + for i = 1:2 + ## FIXME: This looks very likely to break when switching OS + system (["cd ", options.outputDir," && pdflatex ", ofile_name]); + endfor + endif + endif +endfunction + + +function toc_cstr = get_toc (cstr, formatter) + ## GET_TOC extracts the table of contents from a cellstring (e.g., doc.body) + ## with each section headline as a cell in a returned cellstring. + + toc_cstr = cell (); + for i = 1:numel (cstr) + if (strcmp (cstr{i}.type, "section")) + toc_cstr{end+1} = format_text (cstr{i}.content, formatter); + endif + endfor +endfunction + + +function str = format_output (cstr, formatter, options) + ## FORMAT_OUTPUT steps through all blocks (doc.intro or doc.body) in cstr and + ## produces a single result string with the source code in the desired output + ## format. + ## + ## formatter has the only knowledge how to enforce the target format + ## and produces for each block the necessary target format source string. + + str = ""; + for i = 1:numel (cstr) + switch (cstr{i}.type) + case "code" + if (options.showCode) + str = [str, formatter("code", cstr{i}.content)]; + endif + if ((options.evalCode) && (! isempty (cstr{i}.output))) + str = [str, formatter("code_output", cstr{i}.output)]; + endif + case {"text", "section"} + str = [str, formatter(cstr{i}.type, ... + format_text (cstr{i}.content, formatter))]; + case {"bulleted_list", "numbered_list"} + items = cellfun (@(str) format_text(str, formatter), ... + cstr{i}.content, "UniformOutput", false); + str = [str, formatter(cstr{i}.type, items)]; + otherwise + str = [str, formatter(cstr{i}.type, cstr{i}.content)]; + endswitch + endfor + +endfunction + + +function str = format_text (str, formatter) + ## FORMAT_TEXT formats inline formats in strings. + ## These are: links, block/inline math, bold, italic, monospaced, (TM), (R) + + ## Helper to clarify the following regular expressions. It is suitable for + ## inline formatting, that is delimited literally at start and end by + ## `delim`. `term` is an indicating character for the end delimiter. + ## + ## Best explained by example ('^' start and '$' end of input): + ## + ## Positive matches: + ## + ## ^*bold*$ + ## ^*bold*.$ + ## ^(*bold*)$ + ## ^ *bold* $ + ## ^Text *bold* text$ + ## ^*bold text*$ + ## + ## Negative matches: + ## + ## ^Text*bold*text$ + ## ^*bold *$ + ## ^* bold* $ + ## ^*bold text *$ + ## + regex_helper = @(delim, term) ['(^|(?<=\s)|(?=\W))', delim, ... + '(?!\s)[^', term, ']*(?<!\s)', delim, '($|(?=\s)|(?=\W))']; + + ## Regular expressions for the formats: + ## + ## 1) Links "<http://www.someurl.com>" + ## 2) Links "<octave:Function TEXT>" + ## 3) Links "<http://www.someurl.com TEXT>" + ## 4) LaTeX block math "$$x^2$$" + ## 5) LaTeX inline math "$x^2$" + ## 6) Bold *text* + ## 7) Italic _text_ + ## 8) Monospaced |text| + ## 9) (TM) or (R) + regexes = {'<\S{3,}[^\s<>]*>', ... + '<octave:[^\s<>]* *[^<>$]*>', ... + '<\S{3,}[^\s<>]* *[^<>$]*>', ... + regex_helper('\$\$', '$'), ... + regex_helper('\$', '$'), ... + regex_helper('\*', '*'), ... + regex_helper('_', '_'), ... + regex_helper('\|', '|'), ... + '\((TM|R)\)'}; + + ## Function to escape some special characters for the GNU Octave manual, + ## see https://www.gnu.org/software/texinfo/manual/texinfo/html_node/HTML-Xref-Node-Name-Expansion.html + texinfo_esc = @(str) strrep (strrep (str, "-", "_002d"), "_", "_005f"); + + ## Substitute all occurrences with placeholders + placeholder_cstr = {}; + plh = 0; + for i = 1:numel (regexes) + cstr = regexp (str, regexes{i}, "match"); + for j = 1:numel (cstr) + plh += 1; + str = regexprep (str, regexes{i}, ["PUBLISHPLACEHOLDER" num2str(plh)], + "once"); + switch (i) + case 1 + ## Links "<http://www.someurl.com>" + url = cstr{j}; + cstr{j} = formatter ("link", url(2:end-1), url(2:end-1)); + case 2 + ## Links "<octave:Function TEXT>" + idx = strfind (cstr{j}, " "); + url = cstr{j}; + url = texinfo_esc (url(9:idx-1)); + v = version (); + if (v(end) == '+') + v = "interpreter"; + endif + url = sprintf ( ... + "https://www.gnu.org/software/octave/doc/%s/XREF%s.html", v, url); + txt = cstr{j}; + txt = format_text (txt(idx+1:end-1), formatter); + cstr{j} = formatter ("link", url, txt); + case 3 + ## Links "<http://www.someurl.com TEXT>" + idx = strfind (cstr{j}, " "); + url = cstr{j}; + url = url(2:idx-1); + txt = cstr{j}; + txt = format_text (txt(idx+1:end-1), formatter); + cstr{j} = formatter ("link", url, txt); + case 4 + ## LaTeX block math "$$" + txt = cstr{j}; + cstr{j} = formatter ("blockmath", txt(3:end-2)); + case 5 + ## LaTeX inline math "$" + txt = cstr{j}; + cstr{j} = formatter ("inlinemath", txt(2:end-1)); + case 6 + ## Bold + txt = cstr{j}; + cstr{j} = formatter ("bold", format_text (txt(2:end-1), formatter)); + case 7 + ## Italic + txt = cstr{j}; + cstr{j} = formatter ("italic", format_text (txt(2:end-1), formatter)); + case 8 + ## Monospaced + txt = cstr{j}; + cstr{j} = formatter ("monospaced", format_text (txt(2:end-1), ... + formatter)); + case 9 + ## (TM) or (R) + txt = cstr{j}; + cstr{j} = formatter (txt(2:end-1)); + endswitch + endfor + placeholder_cstr = [placeholder_cstr, cstr]; + endfor + + ## Replace special symbols + str = formatter ("escape_special_chars", str); + + ## Restore placeholders + for i = plh:-1:1 + str = strrep (str, ["PUBLISHPLACEHOLDER" sprintf("%d", i)], + placeholder_cstr{i}); + endfor + +endfunction + + +function doc = eval_code (doc, options) + ## EVAL_CODE Third level parsing + ## + ## Generates the output of the script code and takes care of generated + ## figures. + + ## Necessary as the code does not run interactively + page_screen_output (false, "local"); + + ## Remember previously opened figures + fig_ids = findall (0, "type", "figure"); + [~, fig_name] = fileparts (doc.m_source_file_name); + fig_num = 1; + fig_list = struct (); + + ## File used as temporary context + tmp_context = [tempname() ".var"]; + + ## Evaluate code, that does not appear in the output. + eval_code_helper (tmp_context, options.codeToEvaluate); + + ## Create a new figure, if there are existing plots + if (! isempty (fig_ids) && options.useNewFigure) + figure (); + endif + + for i = 1:numel (doc.body) + if (strcmp (doc.body{i}.type, "code")) + r = doc.body{i}.lines; + code_str = strjoin (doc.m_source(r(1):r(2)), "\n"); + if (options.catchError) + try + doc.body{i}.output = eval_code_helper (tmp_context, code_str); + catch err + doc.body{i}.output = cellstr (["error: ", err.message, ... + "\n\tin:\n\n", code_str]); + end_try_catch + else + doc.body{i}.output = eval_code_helper (tmp_context, code_str); + endif + + ## Check for newly created figures ... + fig_ids_new = setdiff (findall (0, "type", "figure"), fig_ids); + ## ... and save them + for j = 1:numel (fig_ids_new) + drawnow (); + if (isempty (get (fig_ids_new(j), "children"))) + continue; + endif + fname = [fig_name, "-", sprintf("%d", fig_num)]; + if (strncmp (options.imageFormat, "eps", 3)) + fname = [fname ".eps"]; + else + fname = [fname "." options.imageFormat]; + endif + print_opts = {fig_ids_new(j), ... + fullfile(options.outputDir, fname), ... + ["-d" options.imageFormat], "-color"}; + if (! isempty (options.maxWidth) && ! isempty (options.maxHeight)) + print_opts{end+1} = sprintf ("-S%d,%d", options.maxWidth, + options.maxHeight); + elseif (! isempty (options.maxWidth) || ! isempty (options.maxHeight)) + warning (["publish: specify both options.maxWidth ", ... + "and options.maxHeight"]); + endif + print (print_opts{:}); + fig_num++; + delete (fig_ids_new(j)); + fig_elem = cell (); + fig_elem{1} = struct ("type", "graphic", "content", fname); + if (isfield (fig_list, num2str (i))) + fig_elem = [getfield(fig_list, sprintf ("%d", i)), fig_elem]; + endif + fig_list = setfield (fig_list, sprintf ("%d", i), fig_elem); + ## Create a new figure, if there are existing plots + if (isempty (setdiff (findall (0, "type", "figure"), fig_ids)) ... + && ! isempty (fig_ids) && options.useNewFigure) + figure (); + endif + endfor + + ## Truncate output to desired length + if (options.maxOutputLines < length (doc.body{i}.output)) + doc.body{i}.output = doc.body{i}.output(1:options.maxOutputLines); + endif + doc.body{i}.output = strjoin (doc.body{i}.output, "\n"); + endif + endfor + + ## Close any figures opened by publish function + delete (setdiff (findall (0, "type", "figure"), fig_ids)); + + ## Remove temporary context + unlink (tmp_context); + + ## Insert figures to document + fig_code_blocks = fieldnames (fig_list); + body_offset = 0; + for i = 1:numel (fig_code_blocks) + elems = getfield (fig_list, fig_code_blocks{i}); + ## Compute index where the figure(s) has to be inserted + j = str2double (fig_code_blocks{i}) + body_offset; + doc.body = [doc.body(1:j), elems, doc.body(j+1:end)]; + body_offset = body_offset + numel (elems); + endfor + +endfunction + + +function cstr = eval_code_helper (context, code) + ## EVAL_CODE_HELPER evaluates a given string with Octave code in an extra + ## temporary context and returns a cellstring with the eval output. + + if (isempty (code)) + return; + endif + + load_snippet = ""; + if (exist (context, "file") == 2) + load_snippet = sprintf ('load ("%s");', context); + endif + save_snippet = sprintf ('save ("-binary", "%s");', context); + + eval (sprintf ("function __eval__ ()\n%s\n%s\n%s\nendfunction", + load_snippet, code, save_snippet)); + + cstr = strsplit (evalc ("__eval__"), "\n"); +endfunction + + +## FIXME: Missing any functional BIST tests +## FIXME: Need to create a temporary file for use with error testing + +## Test input validation +%!error publish () +%!error publish (1) +%!error <FILE does not exist> publish ("%%_non_existent_file_%%.m") +%!error <only script files can be published> publish ("publish.m") +%!error publish ("test_script.m", "format", "html", "showCode")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/saveobj.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,45 @@ +## Copyright (C) 2008-2017 David Bateman +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {@var{b} =} saveobj (@var{a}) +## Method of a class to manipulate an object prior to saving it to a file. +## +## The function @code{saveobj} is called when the object @var{a} is saved +## using the @code{save} function. An example of the use of @code{saveobj} +## might be to remove fields of the object that don't make sense to be saved +## or it might be used to ensure that certain fields of the object are +## initialized before the object is saved. For example: +## +## @example +## @group +## function b = saveobj (a) +## b = a; +## if (isempty (b.field)) +## b.field = initfield (b); +## endif +## endfunction +## @end group +## @end example +## +## @seealso{loadobj, class} +## @end deftypefn + +function b = saveobj (a) + error ('saveobj: not defined for class "%s"', class (a)); +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/miscellaneous/validateattributes.m Thu Dec 28 16:14:37 2017 -0800 @@ -0,0 +1,732 @@ +## Copyright (C) 2013-2017 Carnë Draug +## Copyright (C) 2016 Carlo de Falco +## +## 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 +## <http://www.gnu.org/licenses/>. + +## -*- texinfo -*- +## @deftypefn {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}) +## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{arg_idx}) +## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{func_name}) +## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{func_name}, @var{arg_name}) +## @deftypefnx {} {} validateattributes (@var{A}, @var{classes}, @var{attributes}, @var{func_name}, @var{arg_name}, @var{arg_idx}) +## Check validity of input argument. +## +## Confirms that the argument @var{A} is valid by belonging to one of +## @var{classes}, and holding all of the @var{attributes}. If it does not, +## an error is thrown, with a message formatted accordingly. The error +## message can be made further complete by the function name @var{fun_name}, +## the argument name @var{arg_name}, and its position in the input +## @var{arg_idx}. +## +## @var{classes} must be a cell array of strings (an empty cell array is +## allowed) with the name of classes (remember that a class name is case +## sensitive). In addition to the class name, the following categories +## names are also valid: +## +## @table @asis +## @item @qcode{"float"} +## Floating point value comprising classes @qcode{"double"} and +## @qcode{"single"}. +## +## @item @qcode{"integer"} +## Integer value comprising classes (u)int8, (u)int16, (u)int32, (u)int64. +## +## @item @qcode{"numeric"} +## Numeric value comprising either a floating point or integer value. +## +## @end table +## +## @var{attributes} must be a cell array with names of checks for @var{A}. +## Some of them require an additional value to be supplied right after the +## name (see details for each below). +## +## @table @asis +## @item @qcode{"<="} +## All values are less than or equal to the following value in +## @var{attributes}. +## +## @item @qcode{"<"} +## All values are less than the following value in @var{attributes}. +## +## @item @qcode{">="} +## All values are greater than or equal to the following value in +## @var{attributes}. +## +## @item @qcode{">"} +## All values are greater than the following value in @var{attributes}. +## +## @item @qcode{"2d"} +## A 2-dimensional matrix. Note that vectors and empty matrices have +## 2 dimensions, one of them being of length 1, or both length 0. +## +## @item @qcode{"3d"} +## Has no more than 3 dimensions. A 2-dimensional matrix is a 3-D matrix +## whose 3rd dimension is of length 1. +## +## @item @qcode{"binary"} +## All values are either 1 or 0. +## +## @item @qcode{"column"} +## Values are arranged in a single column. +## +## @item @qcode{"decreasing"} +## No value is @var{NaN}, and each is less than the preceding one. +## +## @item @qcode{"diag"} +## Value is a diagonal matrix. +## +## @item @qcode{"even"} +## All values are even numbers. +## +## @item @qcode{"finite"} +## All values are finite. +## +## @item @qcode{"increasing"} +## No value is @var{NaN}, and each is greater than the preceding one. +## +## @item @qcode{"integer"} +## All values are integer. This is different than using @code{isinteger} +## which only checks its an integer type. This checks that each value in +## @var{A} is an integer value, i.e., it has no decimal part. +## +## @item @qcode{"ncols"} +## Has exactly as many columns as the next value in @var{attributes}. +## +## @item @qcode{"ndims"} +## Has exactly as many dimensions as the next value in @var{attributes}. +## +## @item @qcode{"nondecreasing"} +## No value is @var{NaN}, and each is greater than or equal to the preceding +## one. +## +## @item @qcode{"nonempty"} +## It is not empty. +## +## @item @qcode{"nonincreasing"} +## No value is @var{NaN}, and each is less than or equal to the preceding one. +## +## @item @qcode{"nonnan"} +## No value is a @code{NaN}. +## +## @item @nospell{@qcode{"nonnegative"}} +## All values are non negative. +## +## @item @qcode{"nonsparse"} +## It is not a sparse matrix. +## +## @item @qcode{"nonzero"} +## No value is zero. +## +## @item @qcode{"nrows"} +## Has exactly as many rows as the next value in @var{attributes}. +## +## @item @qcode{"numel"} +## Has exactly as many elements as the next value in @var{attributes}. +## +## @item @qcode{"odd"} +## All values are odd numbers. +## +## @item @qcode{"positive"} +## All values are positive. +## +## @item @qcode{"real"} +## It is a non-complex matrix. +## +## @item @qcode{"row"} +## Values are arranged in a single row. +## +## @item @qcode{"scalar"} +## It is a scalar. +## +## @item @qcode{"size"} +## Its size has length equal to the values of the next in @var{attributes}. +## The next value must is an array with the length for each dimension. To +## ignore the check for a certain dimension, the value of @code{NaN} can be +## used. +## +## @item @qcode{"square"} +## Is a square matrix. +## +## @item @qcode{"vector"} +## Values are arranged in a single vector (column or vector). +## +## @end table +## +## @seealso{isa, validatestring, inputParser} +## @end deftypefn + +function validateattributes (A, cls, attr, varargin) + + if (nargin < 3 || nargin > 6) + print_usage (); + elseif (! iscellstr (cls)) + error ("Octave:invalid-type", + "validateattributes: CLASSES must be a cell array of strings"); + elseif (! iscell (attr)) + error ("Octave:invalid-type", + "validateattributes: ATTRIBUTES must be a cell array"); + endif + + ## Built start of error message from the extra optional arguments + func_name = ""; + var_name = "input"; + if (nargin > 3) + fourth = varargin{1}; + if (ischar (fourth)) + func_name = [fourth ": "]; + elseif (nargin == 4 && valid_arg_idx (fourth)) + var_name = sprintf ("input %d", fourth); + else + error ("Octave:invalid-input-arg", + "validateattributes: 4th input argument must be ARG_IDX or FUNC_NAME"); + endif + + if (nargin > 4) + var_name = varargin{2}; + if (! ischar (var_name)) + error ("Octave:invalid-type", + "validateattributes: VAR_NAME must be a string"); + endif + + if (nargin > 5) + arg_idx = varargin{3}; + if (! valid_arg_idx (arg_idx)) + error ("Octave:invalid-input-arg", + "validateattributes: ARG_IDX must be a positive integer"); + endif + var_name = sprintf ("%s (argument #%i)", var_name, arg_idx); + endif + endif + endif + err_ini = [func_name var_name]; + + check_cl = isa (A, cls); + if (! isempty (check_cl) && ! any (check_cl)) + ## Allowing for an empty list of classes is Matlab incompatible but + ## that should count as a just a Matlab bug, not an incompatibility. + + ## Replace the category names with the classes that belong to it. + integer = { "int8" "int16" "int32" "int64" ... + "uint8" "uint16" "uint32" "uint64"}; + float = {"single" "double"}; + numeric = {integer{:} float{:}}; + cls = replace_cl_group (cls, "integer", integer); + cls = replace_cl_group (cls, "float", float ); + cls = replace_cl_group (cls, "numeric", numeric); + cls = unique (cls); + + classes = sprintf (" %s", cls{:}); + error ("Octave:invalid-type", + "%s must be of class:\n\n %s\n\nbut was of class %s", + err_ini, classes, class (A)); + endif + + ## We use a while loop because some attributes require the following value + ## in the cell array. Also, we can't just get the boolean value for the + ## test and check at the end the error message since some of the tests + ## require some more complex error message. + + ## It may look like that we don't perform enough input check in this + ## function (e.g., we don't check if there's a value after the size + ## attribute). The reasoning is that this will be a function mostly used + ## by developers with fairly static input so any problem would be caught + ## immediately during that function development, it's no dependent on the + ## final user input. In addition, it can be called so many times at the + ## start of every function, we want it to run specially fast. + idx = 1; + problem = false; # becomes true when one of the tests fails + while (idx <= numel (attr)) + ## FIXME: once we use this in Octave core, it might be worthy to find + ## which attributes are checked more often, and place them in that + ## order inside the switch block. + switch (tolower (attr{idx++})) + case "2d", + problem = ndims (A) != 2; + err_id = "Octave:expected-2d"; + case "3d", + problem = ndims (A) > 3; + err_id = "Octave:expected-3d"; + case "column", + problem = ! iscolumn (A); + err_id = "Octave:expected-column"; + case "row", + problem = ! isrow (A); + err_id = "Octave:expected-row"; + case "scalar", + problem = ! isscalar (A); + err_id = "Octave:expected-scalar"; + case "vector", + problem = ! isvector (A); + err_id = "Octave:expected-vector"; + case "square", + problem = ! issquare (A); + err_id = "Octave:expected-square"; + case "diag", + problem = ! isdiag (A); + err_id = "Octave:expected-diag"; + case "nonempty", + problem = isempty (A); + err_id = "Octave:expected-nonempty"; + case "nonsparse", + problem = issparse (A); + err_id = "Octave:expected-nonsparse"; + case "binary", + problem = ! islogical (A) && ... + any ((A(:) != 1) & (A(:) != 0)); + err_id = "Octave:expected-binary"; + case "even", + problem = any (rem (A(:), 2) != 0); + err_id = "Octave:expected-even"; + case "odd", + problem = any (mod (A(:), 2) != 1); + err_id = "Octave:expected-odd"; + case "integer", + problem = ! isinteger (A) && ... + any (ceil (A(:)) != A(:)); + err_id = "Octave:expected-integer"; + case "real", + problem = ! isreal (A); + err_id = "Octave:expected-real"; + case "finite", + problem = ! isinteger (A) && ... + ! all (isfinite (A(:))); + err_id = "Octave:expected-finite"; + case "nonnan", + problem = ! isinteger (A) && ... + any (isnan (A(:))); + err_id = "Octave:expected-nonnan"; + case "nonnegative", + problem = any (A(:) < 0); + err_id = "Octave:expected-nonnegative"; + case "nonzero", + problem = any (A(:) == 0); + err_id = "Octave:expected-nonzero"; + case "positive", + problem = any (A(:) <= 0); + err_id = "Octave:expected-positive"; + case "decreasing", + problem = (any (isnan (A(:))) + || any (diff (A(:)) >= 0)); + err_id = "Octave:expected-decreasing"; + case "increasing", + problem = (any (isnan (A(:))) + || any (diff (A(:)) <= 0)); + err_id = "Octave:expected-increasing"; + case "nondecreasing", + problem = (any (isnan (A(:))) + || any (diff (A(:)) < 0)); + err_id = "Octave:expected-nondecreasing"; + case "nonincreasing", + problem = (any (isnan (A(:))) + || any (diff (A(:)) > 0)); + err_id = "Octave:expected-nonincreasing"; + case "size", + A_size = size (A); + w_size = attr{idx++}; + A_size(isnan (w_size)) = NaN; + if (! isequaln (A_size, w_size)) + A_size_str = sprintf ("%dx", size (A))(1:end-1); + w_size_str = sprintf ("%ix", w_size)(1:end-1); + w_size_str = strrep (w_size_str, "NaN", "N"); + err_id = "Octave:incorrect-size"; + error (err_id, + "%s must be of size %s but was %s", + err_ini, w_size_str, A_size_str); + endif + case "numel", + if (numel (A) != attr{idx++}) + err_id = "Octave:incorrect-numel"; + error (err_id, + "%s must have %d elements", err_ini, attr{idx-1}); + endif + case "ncols", + if (columns (A) != attr{idx++}) + err_id = "Octave:incorrect-numcols"; + error (err_id, + "%s must have %d columns", err_ini, attr{idx-1}); + endif + case "nrows", + if (rows (A) != attr{idx++}) + err_id = "Octave:incorrect-numrows"; + error (err_id, + "%s must have %d rows", err_ini, attr{idx-1}); + endif + case "ndims", + ## Note that a [4 5 1] matrix is not considered to have ndims == 3 + ## but is ok for "3d". This is not a bug. + if (ndims (A) != attr{idx++}) + err_id = "Octave:incorrect-numdims"; + error (err_id, + "%s must have %d dimensions", err_ini, attr{idx-1}); + endif + case ">" + if (! all (A(:) > attr{idx++})) + err_id = "Octave:expected-greater"; + error (err_id, + "%s must be greater than %f", err_ini, attr{idx-1}); + endif + case ">=" + if (! all (A(:) >= attr{idx++})) + err_id = "Octave:expected-greater-equal"; + error (err_id, + "%s must be greater than or equal to %f", err_ini, attr{idx-1}); + endif + case "<" + if (! all (A(:) < attr{idx++})) + err_id = "Octave:expected-less"; + error (err_id, + "%s must be less than %f", err_ini, attr{idx-1}); + endif + case "<=" + if (! all (A(:) <= attr{idx++})) + err_id = "Octave:expected-less-equal"; + error (err_id, + "%s must be less than or equal to %f", err_ini, attr{idx-1}); + endif + otherwise + err_id = "Octave:invalid-input-arg"; + error (err_id, + "validateattributes: unknown ATTRIBUTE %s", attr{idx-1}); + endswitch + if (problem) + error (err_id, + "%s must be %s", err_ini, attr{idx-1}); + endif + endwhile + +endfunction + +function retval = valid_arg_idx (arg) + retval = isnumeric (arg) && isscalar (arg) && arg > 0 && arg == fix (arg); +endfunction + +function cls = replace_cl_group (cls, name, group) + num_pos = strcmpi (cls, name); + if (any (num_pos)) + cls(num_pos) = []; + cls(end+1:end+numel(group)) = group; + endif +endfunction + + +%!error <double> validateattributes (rand (5), {"uint8"}, {}) +%!error <single> validateattributes (uint8 (rand (5)), {"float"}, {}) +%!error <2d> validateattributes (rand (5, 5, 5), {}, {"2d"}) +%!error <3d> validateattributes (rand (5, 5, 5, 7), {}, {"3d"}) +%!error <column> validateattributes (rand (5, 5), {}, {"column"}) +%!error <column> validateattributes (rand (1, 5), {}, {"column"}) +%!error <row> validateattributes (rand (5, 5), {}, {"row"}) +%!error <row> validateattributes (rand (5, 1), {}, {"row"}) +%!error <scalar> validateattributes (rand (1, 5), {}, {"scalar"}) +%!error <vector> validateattributes (rand (5), {}, {"vector"}) +%!error <square> validateattributes (rand (5, 6), {}, {"square"}) +%!error <nonempty> validateattributes ([], {}, {"nonempty"}) +%!error <nonsparse> validateattributes (sparse(rand(5)), {}, {"nonsparse"}) +%!error <binary> validateattributes ("text", {}, {"binary"}) +%!error <binary> validateattributes ([0 1 0 3 0], {}, {"binary"}) +%!error <even> validateattributes ([2 3 6 8], {}, {"even"}) +%!error <even> validateattributes ([2 NaN], {}, {"even"}) +%!error <odd> validateattributes ([3 4 7 5], {}, {"odd"}) +%!error <odd> validateattributes ([5 NaN], {}, {"odd"}) +%!error <integer> validateattributes ([5 5.2 5.7], {}, {"integer"}) +%!error <real> validateattributes ([5i 8 9], {}, {"real"}) +%!error <finite> validateattributes ([5i Inf 8], {}, {"finite"}) +%!error <nonnan> validateattributes ([NaN Inf 8], {}, {"nonnan"}) +%!error <nonnegative> validateattributes ([7 8 -9], {}, {"nonnegative"}) +%!error <nonzero> validateattributes ([7 8 0], {}, {"nonzero"}) +%!error <positive> validateattributes ([7 0 8], {}, {"positive"}) +%!error <decreasing> validateattributes ([7 8 4 3 -5], {}, {"decreasing"}) +%!error <decreasing> validateattributes ([7 NaN 4 3 -5], {}, {"decreasing"}) +%!error <increasing> validateattributes ([7 8 4 9 20], {}, {"increasing"}) +%!error <increasing> validateattributes ([7 8 NaN 9 20], {}, {"increasing"}) +%!error <nonincreasing> validateattributes ([7 8 4 9 20], {}, {"nonincreasing"}) +%!error <nonincreasing> validateattributes ([7 8 NaN 9 20], {}, {"nonincreasing"}) +%!error <nondecreasing> validateattributes ([7 8 4 3 -5], {}, {"nondecreasing"}) +%!error <nondecreasing> validateattributes ([7 NaN 4 3 -5], {}, {"nondecreasing"}) +%!error <size> validateattributes (ones (5, 3, 6), {}, {"size", [5 4 7]}) +%!error <size> validateattributes (ones (5, 3, 6), {}, {"size", [5 NaN 7]}) +%!error <size> validateattributes (ones (5, 3, 6), {}, {"size", [5 3 6 2]}) +%!error <elements> validateattributes (ones (6, 3), {}, {"numel", 12}) +%!error <columns> validateattributes (ones (6, 2), {}, {"ncols", 3}) +%!error <rows> validateattributes (ones (6, 2), {}, {"nrows", 3}) +%!error <dimensions> validateattributes (ones (6, 2, 6, 3), {}, {"ndims", 3}) +%!error <greater than> validateattributes ([6 7 8 5], {}, {">", 5}) +%!error <greater than> validateattributes ([6 7 8 5], {}, {">=", 6}) +%!error <less than> validateattributes ([6 7 8 5], {}, {"<", 8}) +%!error <less than> validateattributes ([6 7 8 5], {}, {"<=", 7}) +%!error <diag> validateattributes ([0 0 0; 0 0 0; 1 0 0], {}, {"diag"}) +%!error <diag> validateattributes (repmat (eye (3), [1 1 3]), {}, {"diag"}) + +%!test +%! validateattributes (rand (5), {"numeric"}, {}); +%! validateattributes (rand (5), {"float"}, {}); +%! validateattributes (rand (5), {"double"}, {}); +%! validateattributes ("text", {"char"}, {}); +%! validateattributes (rand (5), {}, {"2d"}); +%! validateattributes (rand (5), {}, {"3d"}); +%! validateattributes (rand (5, 5, 5), {}, {"3d"}); +%! validateattributes (rand (5, 1), {}, {"column"}); +%! validateattributes (rand (1, 5), {}, {"row"}); +%! validateattributes ("a", {}, {"scalar"}); +%! validateattributes (5, {}, {"scalar"}); +%! validateattributes (rand (1, 5), {}, {"vector"}); +%! validateattributes (rand (5, 1), {}, {"vector"}); +%! validateattributes (rand (5), {}, {"square"}); +%! validateattributes (rand (5), {}, {"nonempty"}); +%! validateattributes (rand (5), {}, {"nonsparse"}); +%! validateattributes ([0 1 0 1 0], {}, {"binary"}); +%! validateattributes (rand (5) > 0.5, {}, {"binary"}); +%! validateattributes ([8 4 0 6], {}, {"even"}); +%! validateattributes ([-1 3 5], {}, {"odd"}); +%! validateattributes ([8 4 0 6], {}, {"real"}); +%! validateattributes ([8 4i 0 6], {}, {"finite"}); +%! validateattributes (uint8 ([8 4]), {}, {"finite"}); +%! validateattributes ([8 Inf], {}, {"nonnan"}); +%! validateattributes ([0 7 4], {}, {"nonnegative"}); +%! validateattributes ([-8 7 4], {}, {"nonzero"}); +%! validateattributes ([8 7 4], {}, {"positive"}); +%! validateattributes ([8 7 4 -5], {}, {"decreasing"}); +%! validateattributes ([-8 -7 4 5], {}, {"increasing"}); +%! validateattributes ([8 4 4 -5], {}, {"nonincreasing"}); +%! validateattributes ([-8 -8 4 5], {}, {"nondecreasing"}); +%! validateattributes (rand (4, 6, 7, 2), {}, {"size", [4 6 7 2]}); +%! validateattributes (rand (4, 6, 7, 2), {}, {"size", [4 NaN 7 2]}); +%! validateattributes (rand (4, 6, 7, 2), {}, {"size", [4 6 NaN 2 NaN]}); +%! validateattributes (rand (6, 2), {}, {"numel", 12}); +%! validateattributes (rand (6, 2), {}, {"ncols", 2}); +%! validateattributes (rand (6, 2), {}, {"nrows", 6}); +%! validateattributes (rand (6, 2, 4, 5), {}, {"ndims", 4}); +%! validateattributes ([4 5 6 7], {}, {">", 3}); +%! validateattributes ([4 5 6 7], {}, {">=", 4}); +%! validateattributes ([4 5 6 7], {}, {"<", 8}); +%! validateattributes ([4 5 6 7], {}, {"<=", 7}); +%! validateattributes (eye (3), {}, {"diag"}); +%! validateattributes ([1 0 0; 0 1 0; 0 0 1], {}, {"diag"}); +%! validateattributes (zeros (3), {}, {"diag"}); + +%!test +%! validateattributes ([0 1 0 1], {"double", "uint8"}, {"binary", "size", [NaN 4], "nonnan"}); + +%!test +%! try validateattributes (ones(1,2,3), {"numeric"}, {"2d"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-2d"); +%! end_try_catch + +%!test +%! try validateattributes (ones(1,2,3,4), {"numeric"}, {"3d"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-3d"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"column"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-column"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2].', {"numeric"}, {"row"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-row"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"scalar"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-scalar"); +%! end_try_catch + +%!test +%! try validateattributes (ones(3), {"numeric"}, {"vector"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-vector"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"size", [1 1]}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:incorrect-size"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"numel", 7}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:incorrect-numel"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"ncols", 7}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:incorrect-numcols"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"nrows", 7}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:incorrect-numrows"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"ndims", 5}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:incorrect-numdims"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"square"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-square"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"diag"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-diag"); +%! end_try_catch + +%!test +%! try validateattributes ([], {"numeric"}, {"nonempty"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nonempty"); +%! end_try_catch + +%!test +%! try validateattributes (speye(2), {"numeric"}, {"nonsparse"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nonsparse"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {">", 3}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-greater"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {">=", 3}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-greater-equal"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"<", -3}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-less"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"<=", -3}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-less-equal"); +%! end_try_catch + +%!test +%! try validateattributes (3, {"numeric"}, {"binary"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-binary"); +%! end_try_catch + +%!test +%! try validateattributes (1, {"numeric"}, {"even"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-even"); +%! end_try_catch + +%!test +%! try validateattributes (2, {"numeric"}, {"odd"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-odd"); +%! end_try_catch + +%!test +%! try validateattributes (1.1, {"numeric"}, {"integer"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-integer"); +%! end_try_catch + +%!test +%! try validateattributes (1+1i*2, {"numeric"}, {"real"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-real"); +%! end_try_catch + +%!test +%! try validateattributes (Inf, {"numeric"}, {"finite"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-finite"); +%! end_try_catch + +%!test +%! try validateattributes (NaN, {"numeric"}, {"nonnan"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nonnan"); +%! end_try_catch + +%!test +%! try validateattributes (-1, {"numeric"}, {"nonnegative"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nonnegative"); +%! end_try_catch + +%!test +%! try validateattributes (0, {"numeric"}, {"nonzero"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nonzero"); +%! end_try_catch + +%!test +%! try validateattributes (-1, {"numeric"}, {"positive"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-positive"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"decreasing"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-decreasing"); +%! end_try_catch + +%!test +%! try validateattributes ([2 1], {"numeric"}, {"increasing"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-increasing"); +%! end_try_catch + +%!test +%! try validateattributes ([1 0], {"numeric"}, {"nondecreasing"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nondecreasing"); +%! end_try_catch + +%!test +%! try validateattributes ([1 2], {"numeric"}, {"nonincreasing"}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:expected-nonincreasing"); +%! end_try_catch + +%!test +%! try validateattributes (@sin, {"numeric"}, {}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:invalid-type"); +%! end_try_catch + +%!test +%! try validateattributes (@sin, 1, {}); +%! catch id, +%! assert (getfield (id, "identifier"), "Octave:invalid-type"); +%! end_try_catch