diff scripts/miscellaneous/publish.m @ 25749:af43eb4e6502

publish.m: Improve code evaluation and add classdef support. * scripts/miscellaneous/publish.m (eval_context): New subfunction. The previous code evaluation relied on storing variables to a temporary location, using save() and load() to emulate a temporary function context. Using eval_context() a solution inspired by the Octave manual has been chosen: https://octave.org/doc/v4.4.0/Evaluation-in-a-Different-Context.html. Instead of saving to a temporary hdd location, the data is saved in memory inside a persistent variable that is cleared at the end of code publishing. Somehow it is reverting the changes by Mike in cset ab1aa0e57b72, but with the new approach the variable dependency is reduced to a single instance, which is manageable. Especially the "clear all"-bug (bug #51096) is no problem for this new solution. The pros of this solution are: + No dependency on temporary files + No file I/O for code evaluation + Support for not-storable data types (classdef objects, ...)
author Kai T. Ohlhus <k.ohlhus@gmail.com>
date Tue, 07 Aug 2018 13:05:31 +0200
parents ac386820f2b6
children e30a2492eb85
line wrap: on
line diff
--- a/scripts/miscellaneous/publish.m	Mon Aug 06 21:36:00 2018 +0200
+++ b/scripts/miscellaneous/publish.m	Tue Aug 07 13:05:31 2018 +0200
@@ -393,6 +393,7 @@
 
   if (options.evalCode)
     doc = eval_code (doc, options);
+    eval_context ("clear");
   endif
 
   output_file = create_output (doc, options);
@@ -959,11 +960,8 @@
   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);
+  eval_code_helper (options.codeToEvaluate);
 
   ## Create a new figure, if there are existing plots
   if (! isempty (fig_ids) && options.useNewFigure)
@@ -976,13 +974,13 @@
       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);
+          doc.body{i}.output = eval_code_helper (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);
+        doc.body{i}.output = eval_code_helper (code_str);
       endif
 
       ## Check for newly created figures ...
@@ -1036,9 +1034,6 @@
   ## 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;
@@ -1053,29 +1048,59 @@
 endfunction
 
 
-function cstr = eval_code_helper (context, code)
+function cstr = eval_code_helper (__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))
+  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");
+  eval_context ("load");
+  cstr = evalc (__code__);
+  # Split string by lines and preserve blank lines.
+  cstr = strsplit (strrep (cstr, "\n\n", "\n \n"), "\n");
+  eval_context ("save");
 endfunction
 
 
-## FIXME: Missing any functional BIST tests
-## FIXME: Need to create a temporary file for use with error testing
+function cstr = eval_context (op)
+  ## EVAL_CONTEXT temporary evaluation context.
+  persistent ctext
+  
+  # Variable cstr in "eval_code_helper" is newly created anyways.
+  forbidden_var_names = {"__code__"};
+
+  switch (op)
+    case "save"
+      # Clear previous context
+      ctext = containers.Map;
+      # Get variable names
+      var_names = evalin ("caller", "whos");
+      var_names = {var_names.name};
+      # Store all variables to context
+      for i = 1:length (var_names)
+        if (~any (strcmp (var_names{i}, forbidden_var_names)))
+          ctext(var_names{i}) = evalin ("caller", var_names{i});
+        end
+      endfor
+    case "load"
+      if (~isempty (ctext))
+        keys = ctext.keys ();
+        for i = 1:length (keys)
+          assignin ("caller", keys{i}, ctext(keys{i}));
+        endfor
+      endif
+    case "clear"
+      # Clear any context
+      ctext = [];
+    otherwise
+      # Do nothing
+  endswitch
+endfunction
+
+
+## Note: Functional BIST tests are located in the `test/publish` directory.
 
 ## Test input validation
 %!error publish ()