# HG changeset patch # User Kai T. Ohlhus # Date 1468575976 -7200 # Node ID b6f482e29afdc10af70a1aa5a9ce5126fe733edd # Parent 03764eef9f7c9fb1b7d403888b77680e426a5897 New functions publish.m and grabcode.m (patch #9048). * scripts/general/module.mk: Add entries for the new funtions. * scripts/general/grabcode.m: New function. * scripts/general/publish.m: New function. * scripts/general/private/__publish_html_output__.m: New function. * scripts/general/private/__publish_latex_output__.m: New function. * scripts/help/__unimplemented__.m: Remove entries publish and grabcode. * NEWS: Announce new functions. * doc/interpreter/func.txi: Add documentation for the new functions. * test/module.mk: New entry for test module publish. * test/publish/module.mk: New entries for publish tests. * test/publish/publish.tst: New test file, to run all test scripts on publish and grabcode. * test/publish/test_script.m: New test script. * test/publish/test_script_code_only.m: New test script. * test/publish/test_script_empty.m: New test script. * test/publish/test_script_example.m: New test script. * test/publish/test_script_head_only.m: New test script. diff -r 03764eef9f7c -r b6f482e29afd NEWS --- a/NEWS Thu Jul 14 19:56:44 2016 -0400 +++ b/NEWS Fri Jul 15 11:46:16 2016 +0200 @@ -93,6 +93,11 @@ ** The graphics property 'graphicssmothing' for figures now controls whether anti-aliasing will be use for lines. The default is "on". + ** The publish function allows easy publication of Octave script files + in HTML or other formats, including figures and output created by this + script. It comes with its counterpart grabcode, which lets one literally + grab the HTML published code from a remote website, for example. + ** Other new functions added in 4.2: audioformats diff -r 03764eef9f7c -r b6f482e29afd doc/interpreter/func.txi --- a/doc/interpreter/func.txi Thu Jul 14 19:56:44 2016 -0400 +++ b/doc/interpreter/func.txi Fri Jul 15 11:46:16 2016 +0200 @@ -1265,6 +1265,361 @@ @DOCSTRING(source) +@menu +* Publish Octave script files:: +* Publishing Markup:: +@end menu + +@node Publish Octave script files +@subsection Publish Octave script files + +The function @code{publish} provides a dynamic possibility to document your +script file. Unlike static documentation, @code{publish} runs the script +file, saves any figures and output while running the script, and presents them +alongside static documentation in a desired output format. The static +documentation can make use of @ref{Publishing Markup} to enhance and +customize the output. + +@DOCSTRING(publish) + +The counterpart to @code{publish} is @code{grabcode}: + +@DOCSTRING(grabcode) + +@node Publishing Markup +@subsection Publishing Markup + +@menu +* Using Publishing Markup in script files:: +* Text formatting:: +* Sections:: +* Preformatted code:: +* Preformatted text:: +* Bulleted lists:: +* Numbered lists:: +* Including file content:: +* Including graphics:: +* Including URLs:: +* Mathematical equations:: +* HTML markup:: +* LaTeX markup:: +@end menu + +@node Using Publishing Markup in script files +@subsubsection Using Publishing Markup in script files + +To use Publishing Markup, start by typing @samp{##} or @samp{%%} at the +beginning of a new line. For Matlab compatibility @samp{%%} is treated the +same way as @samp{%%}. + +The lines following @samp{##} or @samp{%%} start with only one of either +@samp{#} or @samp{%} followed by at least one space. These lines are +interpreted as section. A section ends with the first line not starting +with @samp{#} or @samp{%} or the end of the document is reached. + +A section starting in the first line of the document, followed by another +start of a section that might be empty, is interpreted as a document +title and introduction text. + +See the example below for clarity: + +@example +@group +%% Headline title +% +% Some *bold*, _italic_, or |monospaced| Text with +% a . +%% + +# "Real" Octave commands to be evaluated +sombrero () + +## Octave comment style supported as well +# +# * Bulleted list item 1 +# * Bulleted list item 2 +# +# # Numbered list item 1 +# # Numbered list item 2 +@end group +@end example + +@node Text formatting +@subsubsection Text formatting + +Basic text formatting is supported inside sections, see the example +given below: + +@example +@group +## +# @b{*bold*}, @i{_italic_}, or |monospaced| Text +@end group +@end example + +Additionally two trademark symbols are supported, just embrace the letters +@samp{TM} or @samp{R}. + +@example +@group +## +# (TM) or (R) +@end group +@end example + +@node Sections +@subsubsection Sections + +A section is started by typing @samp{##} or @samp{%%} at the beginning of +a new line. A section title can be provided by writing it, separated by a +space, in the first line after @samp{##} or @samp{%%}. Without a section +title, the section is interpreted as a continuation of the previous section. +For Matlab compatibility @samp{%%} is treated the same way as @samp{%%}. + +@example +@group +some_code (); + +## Section 1 +# +## Section 2 + +some_code (); + +## +# Still in section 2 + +some_code (); + +### Section 3 +# +# +@end group +@end example + +@node Preformatted code +@subsubsection Preformatted code + +To write preformatted code inside a section, indent the code by three +spaces after @samp{#} at the beginning of each line and leave the lines +above and below the code blank, except for @samp{#} at the beginning of +those lines. + +@example +@group +## +# This is a syntax highlighted for-loop: +# +# for i = 1:5 +# disp (i); +# endfor +# +# And more usual text. +@end group +@end example + +@node Preformatted text +@subsubsection Preformatted text + +To write preformatted text inside a section, indent the code by two spaces +after @samp{#} at the beginning of each line and leave the lines above and +below the preformatted text blank, except for @samp{#} at the beginning of +those lines. + +@example +@group +## +# This following text is preformatted: +# +# "To be, or not to be: that is the question: +# Whether 'tis nobler in the mind to suffer +# The slings and arrows of outrageous fortune, +# Or to take arms against a sea of troubles, +# And by opposing end them? To die: to sleep;" +# +# --"Hamlet" by W. Shakespeare +@end group +@end example + +@node Bulleted lists +@subsubsection Bulleted lists + +To create a bulleted list, type + +@example +@group +## +# +# * Bulleted list item 1 +# * Bulleted list item 2 +# +@end group +@end example + +to get output like + +@itemize @bullet +@item Bulleted list item 1 +@item Bulleted list item 2 +@end itemize + +Notice the blank lines, except for the @samp{#} or @samp{%} before and +after the bulleted list! + +@node Numbered lists +@subsubsection Numbered lists + +To create a numbered list, type + +@example +@group +## +# +# # Numbered list item 1 +# # Numbered list item 2 +# +@end group +@end example + +to get output like + +@enumerate +@item Numbered list item 1 +@item Numbered list item 2 +@end enumerate + +Notice the blank lines, except for the @samp{#} or @samp{%} before and +after the numbered list! + +@node Including file content +@subsubsection Including file content + +To include the content of an external file, e.g. a file called +@samp{my_function.m} at the same location as the published Octave script, +use the following syntax to include it with Octave syntax highlighting. + +Alternatively, you can write the full or relative path to the file. + +@example +@group +## +# +# my_function.m +# +# /full/path/to/my_function.m +# +# ../relative/path/to/my_function.m +# +@end group +@end example + +@node Including graphics +@subsubsection Including graphics + +To include external graphics, e.g. a graphic called @samp{my_graphic.png} +at the same location as the published Octave script, use the following syntax. + +Alternatively, you can write the full path to the graphic. + +@example +@group +## +# +# <> +# +# <> +# +# <<../relative/path/to/my_graphic.png>> +# +@end group +@end example + +@node Including URLs +@subsubsection Including URLs + +Basically, a URL is written between an opening @samp{<} and a closing @samp{>} +angle. + +@example +@group +## +# +@end group +@end example + +Text that is within these angles and separated by at least one space from the +URL is a displayed text for the link. + +@example +@group +## +# +@end group +@end example + +A link starting with @samp{ +@end group +@end example + +@node Mathematical equations +@subsubsection Mathematical equations + +One can insert LaTeX inline math, surrounded by single @samp{$} signs, or +displayed math, surrounded by double @samp{$$} signs, directly inside +sections. + +@example +@group +## +# Some shorter inline equation $e^@{ix@} = \cos x + i\sin x$. +# +# Or more complicated formulas as displayed math: +# $$e^x = \lim_@{n\rightarrow\infty@}\left(1+\dfrac@{x@}@{n@}\right)^@{n@}.$$ +@end group +@end example + +@node HTML markup +@subsubsection HTML markup + +If the published output is a HTML report, you can insert HTML markup, +that is only visible in this kind of output. + +@example +@group +## +# +# +# +# +# +@end group +@end example + +@node LaTeX markup +@subsubsection LaTeX markup + +If the published output is a LaTeX or PDF report, you can insert LaTeX markup, +that is only visible in this kind of output. + +@example +@group +## +# +# Some output only visible in \LaTeX or PDF reports. +# \begin@{equation@} +# e^x = \lim\limits_@{n\rightarrow\infty@}\left(1+\dfrac@{x@}@{n@}\right)^@{n@} +# \end@{equation@} +# +@end group +@end example + @node Function Handles Anonymous Functions Inline Functions @section Function Handles, Anonymous Functions, Inline Functions @cindex handle, function handles diff -r 03764eef9f7c -r b6f482e29afd scripts/general/grabcode.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/general/grabcode.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,105 @@ +## Copyright (C) 2016 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 +## . + +## -*- texinfo -*- +## @deftypefn {} {} grabcode (@var{url}) +## @deftypefnx {} {@var{code_str} =} grabcode (@var{url}) +## +## Grab by the @code{publish} function generated HTML reports from Octave +## script files. +## +## The input parameter @var{url} must point to a local or remote HTML file +## with extension @samp{.htm} or @samp{.html} which was generated by the +## @code{publish} function. With any other HTML file this will not work! +## +## If no return value is given, the grabbed code is saved to a temporary +## file and opened in the default editor. +## +## NOTE: You have to save the file at another location with arbitrary name, +## otherwise any grabbed code will be lost! +## +## With a return value given, the grabbed code will be returned as string +## @var{code_str}. +## +## An example: +## +## @example +## @group +## publish ("my_script.m"); +## grabcode ("html/my_script.html"); +## @end group +## @end example +## +## The example above publishes @samp{my_script.m} by default to +## @samp{html/my_script.html}. Afterwards this published Octave script +## is grabbed to edit its content in a new temporary file. +## +## @seealso{publish} +## @end deftypefn + +function varargout = grabcode (url) + narginchk (1, 1); + nargoutchk (0, 1); + + [~,~,ext] = fileparts (url); + if (! strncmp (ext, ".htm", 4)) + error ("grabcode: URL should point to a published \".html\"-file"); + endif + + ## If url is a local file + if (exist (url) == 2) + oct_code = fileread (url); + ## Otherwise try to read a url + else + [oct_code, success, message] = urlread (url); + if (! success) + error (["grabcode: ", message]); + endif + endif + + ## Extract relevant part + start_str = "##### SOURCE BEGIN #####"; + end_str = "##### SOURCE END #####"; + oct_code = oct_code(strfind (oct_code, start_str) + length(start_str) + 1: ... + strfind (oct_code, end_str)-1); + + ## Return Octave code string ... + if (nargout == 1) + varargout{1} = oct_code; + ## ... or open temporary file in editor + else + fname = [tempname(), ".m"]; + fid = fopen (fname, "w"); + if (fid < 0) + error ("grabcode: cannot 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 + +## Bad function calls + +%!error grabcode () +%!error grabcode (1) +%!error grabcode ("html/test_script.html", "pdf") +%!error [str1, str2] = grabcode ("html/test_script.html") \ No newline at end of file diff -r 03764eef9f7c -r b6f482e29afd scripts/general/module.mk --- a/scripts/general/module.mk Thu Jul 14 19:56:44 2016 -0400 +++ b/scripts/general/module.mk Fri Jul 15 11:46:16 2016 +0200 @@ -4,6 +4,8 @@ scripts_general_PRIVATE_FCN_FILES = \ scripts/general/private/__isequal__.m \ + scripts/general/private/__publish_html_output__.m \ + scripts/general/private/__publish_latex_output__.m \ scripts/general/private/__splinen__.m scripts_general_FCN_FILES = \ @@ -35,6 +37,7 @@ scripts/general/flipdim.m \ scripts/general/fliplr.m \ scripts/general/flipud.m \ + scripts/general/grabcode.m \ scripts/general/gradient.m \ scripts/general/idivide.m \ scripts/general/inputParser.m \ @@ -60,6 +63,7 @@ scripts/general/polyarea.m \ scripts/general/postpad.m \ scripts/general/prepad.m \ + scripts/general/publish.m \ scripts/general/quadgk.m \ scripts/general/quadl.m \ scripts/general/quadv.m \ diff -r 03764eef9f7c -r b6f482e29afd scripts/general/private/__publish_html_output__.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/general/private/__publish_html_output__.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,283 @@ +function outstr = __publish_html_output__ (varargin) + ## + ## Types to handle are: + ## + ## * "header" (title_str, intro_str, toc_cstr) + ## * "footer" () + ## * "code" (str) + ## * "code_output" (str) + ## * "section" (str) + ## * "preformatted_code" (str) + ## * "preformatted_text" (str) + ## * "bulleted_list" (cstr) + ## * "numbered_list" (cstr) + ## * "graphic" (str) + ## * "html" (str) + ## * "latex" (str) + ## * "text" (str) + ## * "bold" (str) + ## * "italic" (str) + ## * "monospaced" (str) + ## * "link" (url_str, url_str, str) + ## * "TM" () + ## * "R" () + ## + eval (["outstr = handle_", varargin{1}, " (varargin{2:end});"]); +endfunction + +function outstr = handle_header (title_str, intro_str, toc_cstr) + mathjax_str = ["\n", ... + "\n"]; + stylesheet_str = ["\n"]; + outstr = ["\n", ... + "\n", ... + "\n", ... + "\n", ... + "", title_str, "\n", ... + mathjax_str, ... + stylesheet_str, ... + "\n", ... + "\n", ... + "

", title_str, "

\n", ... + intro_str]; + + if (! isempty (toc_cstr)) + for i = 1:length(toc_cstr) + toc_cstr{i} = handle_link (["#node", num2str(i)], toc_cstr{i}); + endfor + outstr = [outstr, "

Contents

", ... + handle_bulleted_list(toc_cstr)]; + endif + + ## Reset section counter + handle_section (); +endfunction + +function outstr = handle_footer (m_source_str) + outstr = ["\n", ... + "\n", ... + "\n", ... + "\n", ... + "\n"]; +endfunction + +function outstr = handle_code (str) + outstr = ["
", syntax_highlight(str), "
"]; +endfunction + +function outstr = handle_code_output (str) + outstr = ["
", str, "
"]; +endfunction + +function outstr = handle_section (varargin) + persistent counter = 1; + if (nargin == 0) + counter = 1; + outstr = ""; + return; + endif + outstr = ["

", varargin{1}, ... + "

"]; + counter++; +endfunction + +function outstr = handle_preformatted_code (str) + outstr = ["
", syntax_highlight(str), "
"]; +endfunction + +function outstr = handle_preformatted_text (str) + outstr = ["
", str, "
"]; +endfunction + +function outstr = handle_bulleted_list (cstr) + outstr = "
    "; + for i = 1:length(cstr) + outstr = [outstr, "
  • ", cstr{i}, "
  • "]; + endfor + outstr = [outstr, "
"]; +endfunction + +function outstr = handle_numbered_list (cstr) + outstr = "
    "; + for i = 1:length(cstr) + outstr = [outstr, "
  1. ", cstr{i}, "
  2. "]; + endfor + outstr = [outstr, "
"]; +endfunction + +function outstr = handle_graphic (str) + outstr = ["\"","]; +endfunction + +function outstr = handle_html (str) + outstr = str; +endfunction + +function outstr = handle_latex (str) + outstr = ""; +endfunction + +function outstr = handle_link (url_str, str) + outstr = ["", str, ""]; +endfunction + +function outstr = handle_text (str) + outstr = ["

", str, "

"]; +endfunction + +function outstr = handle_bold (str) + outstr = ["", str, ""]; +endfunction + +function outstr = handle_italic (str) + outstr = ["", str, ""]; +endfunction + +function outstr = handle_monospaced (str) + outstr = ["", str, ""]; +endfunction + +function outstr = handle_TM () + outstr = "™"; +endfunction + +function outstr = handle_R () + outstr = "®"; +endfunction + +function outstr = syntax_highlight (str) + ## SYNTAX_HIGHLIGHT a primitive parser to add syntax highlight via + ## tags. Should be replaced by a better solution. + ## + + outstr = ""; + i = 1; + while (i <= length(str)) + ## Block comment + if (any (strncmp (str(i:end), {"%{", "#{"}, 2))) + outstr = [outstr, "", str(i:i+1)]; + i = i + 2; + while ((i <= length(str)) ... + && ! (any (strncmp (str(i:end), {"%}", "#}"}, 2)))) + outstr = [outstr, str(i)]; + i++; + endwhile + if (i < length(str)) + outstr = [outstr, str(i:i+1), ""]; + i = i + 2; + else + outstr = [outstr, ""]; + endif + ## Line comment + elseif (any (strcmp (str(i), {"%", "#"}))) + outstr = [outstr, ""]; + while ((i <= length(str)) && (! strcmp (str(i), "\n"))) + outstr = [outstr, str(i)]; + i++; + endwhile + outstr = [outstr, "\n"]; + i++; + ## Single quoted string + elseif (strcmp (str(i), "'")) + outstr = [outstr, "'"]; + i++; + while (i <= length(str)) + ## Ignore escaped string terminations + if (strncmp (str(i:end), "''", 2)) + outstr = [outstr, "''"]; + i = i + 2; + ## Is string termination + elseif (strcmp (str(i), "'")) + outstr = [outstr, "'"]; + i++; + break; + ## String content + else + outstr = [outstr, str(i)]; + i++; + endif + endwhile + ## Double quoted string + elseif (strcmp (str(i), "\"")) + outstr = [outstr, "\""]; + i++; + while (i <= length(str)) + ## Is string termination + if (strcmp (str(i), "\"") && ! strcmp (str(i - 1), "\\")) + outstr = [outstr, "\""]; + i++; + break; + ## String content + else + outstr = [outstr, str(i)]; + i++; + endif + endwhile + else + outstr = [outstr, str(i)]; + iskeyword ("if"); + i++; + endif + endwhile + kwords = iskeyword (); + for i = 1:length(kwords) + outstr = regexprep (outstr, ... + ['(?!]*?>)(\s|^)(', kwords{i},')(\s|$)(?![^<]*?<\/span>)'], ... + ["$1$2$3"]); + endfor +endfunction \ No newline at end of file diff -r 03764eef9f7c -r b6f482e29afd scripts/general/private/__publish_latex_output__.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/general/private/__publish_latex_output__.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,158 @@ +function outstr = __publish_latex_output__ (varargin) + ## + ## Types to handle are: + ## + ## * "header" (title_str, intro_str, toc_cstr) + ## * "footer" () + ## * "code" (str) + ## * "code_output" (str) + ## * "section" (str) + ## * "preformatted_code" (str) + ## * "preformatted_text" (str) + ## * "bulleted_list" (cstr) + ## * "numbered_list" (cstr) + ## * "graphic" (str) + ## * "html" (str) + ## * "latex" (str) + ## * "text" (str) + ## * "bold" (str) + ## * "italic" (str) + ## * "monospaced" (str) + ## * "link" (url_str, url_str, str) + ## * "TM" () + ## * "R" () + ## + eval (["outstr = handle_", varargin{1}, " (varargin{2:end});"]); +endfunction + +function outstr = handle_header (title_str, intro_str, toc_cstr) + publish_comment = ["\n\n", ... + "% This document was generated by the publish-function\n", ... + "% from GNU Octave ", version(), "\n\n"]; + + latex_preamble = ["\n\n", ... + "\\documentclass[10pt]{article}\n", ... + "\\usepackage{listings}\n", ... + "\\usepackage{mathtools}\n", ... + "\\usepackage{graphicx}\n", ... + "\\usepackage{hyperref}\n", ... + "\\usepackage{xcolor}\n", ... + "\\usepackage{titlesec}\n", ... + "\\usepackage[utf8]{inputenc}\n", ... + "\\usepackage[T1]{fontenc}\n", ... + "\\usepackage{lmodern}\n"]; + + listings_option = ["\n\n", ... + "\\lstset{\n", ... + "language=Octave,\n", ... + "numbers=none,\n", ... + "frame=single,\n", ... + "tabsize=2,\n", ... + "showstringspaces=false,\n", ... + "breaklines=true}\n"]; + + ## Escape "_" in title_str, as many file names contain it + latex_head = ["\n\n", ... + "\\titleformat*{\\section}{\\Huge\\bfseries}\n", ... + "\\titleformat*{\\subsection}{\\large\\bfseries}\n", ... + "\\renewcommand{\\contentsname}{\\Large\\bfseries Contents}\n", ... + "\\setlength{\\parindent}{0pt}\n\n",... + "\\begin{document}\n\n", ... + "{\\Huge\\section*{", escape_chars(title_str),"}}\n\n", ... + "\\tableofcontents\n", ... + "\\vspace*{4em}\n\n"]; + + outstr = [publish_comment, latex_preamble, listings_option, latex_head]; +endfunction + +function outstr = handle_footer (m_source_str) + outstr = ["\n\n\\end{document}\n"]; +endfunction + +function outstr = handle_code (str) + outstr = ["\\begin{lstlisting}\n", str, "\n\\end{lstlisting}\n"]; +endfunction + +function outstr = handle_code_output (str) + outstr = ["\\begin{lstlisting}", ... + "[language={},xleftmargin=5pt,frame=none]\n", ... + str, "\n\\end{lstlisting}\n"]; +endfunction + +function outstr = handle_section (str) + outstr = ["\n\n\\phantomsection\n", ... + "\\addcontentsline{toc}{section}{", str, "}\n", ... + "\\subsection*{", str, "}\n\n"]; +endfunction + +function outstr = handle_preformatted_code (str) + outstr = ["\\begin{lstlisting}\n", str, "\n\\end{lstlisting}\n"]; +endfunction + +function outstr = handle_preformatted_text (str) + outstr = ["\\begin{lstlisting}[language={}]\n", ... + str, "\n\\end{lstlisting}\n"]; +endfunction + +function outstr = handle_bulleted_list (cstr) + outstr = "\n\\begin{itemize}\n"; + for i = 1:length(cstr) + outstr = [outstr, "\\item ", cstr{i}, "\n"]; + endfor + outstr = [outstr, "\\end{itemize}\n"]; +endfunction + +function outstr = handle_numbered_list (cstr) + outstr = "\n\\begin{enumerate}\n"; + for i = 1:length(cstr) + outstr = [outstr, "\\item ", cstr{i}, "\n"]; + endfor + outstr = [outstr, "\\end{enumerate}\n"]; +endfunction + +function outstr = handle_graphic (str) + outstr = ["\\begin{figure}[!ht]\n", ... + "\\includegraphics[width=\\textwidth]{", str, "}\n", ... + "\\end{figure}\n"]; +endfunction + +function outstr = handle_html (str) + outstr = ""; +endfunction + +function outstr = handle_latex (str) + outstr = str; +endfunction + +function outstr = handle_link (url_str, str) + outstr = ["\\href{", url_str,"}{", str, "}"]; +endfunction + +function outstr = handle_text (str) + outstr = ["\n\n", str, "\n\n"]; +endfunction + +function outstr = handle_bold (str) + outstr = ["\\textbf{", str, "}"]; +endfunction + +function outstr = handle_italic (str) + outstr = ["\\textit{", str, "}"]; +endfunction + +function outstr = handle_monospaced (str) + outstr = ["\\texttt{", str, "}"]; +endfunction + +function outstr = handle_TM () + outstr = "\\texttrademark"; +endfunction + +function outstr = handle_R () + outstr = "\\textregistered"; +endfunction + +function str = escape_chars (str) + ## Escape "_" + str = regexprep (str, '([^\\]|^)(_)', "$1\\_"); +endfunction diff -r 03764eef9f7c -r b6f482e29afd scripts/general/publish.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/general/publish.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,971 @@ +## Copyright (C) 2016 Kai T. Ohlhus +## Copyright (C) 2010 Fotios Kasolis +## +## 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 +## . + +## -*- texinfo -*- +## @deftypefn {} {} publish (@var{filename}) +## @deftypefnx {} {} publish (@var{filename}, @var{output_format}) +## @deftypefnx {} {} publish (@var{filename}, @var{option1}, @var{value1}, @dots{}) +## @deftypefnx {} {} publish (@var{filename}, @var{options}) +## @deftypefnx {} {@var{output_file} =} publish (@var{filename}, @dots{}) +## +## Generate reports from Octave script files in several output formats. +## +## The generated reports consider 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 content +## of a script file named @samp{example.m}: +## +## @example +## @group +## %% Headline title +## % +## % Some *bold*, _italic_, or |monospaced| Text with +## % a . +## %% +## +## # "Real" Octave commands to be evaluated +## sombrero () +## +## ## Octave comment style 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 ("example.m")}. +## +## With only @var{filename} given, a HTML report is generated in a +## subdirectory @samp{html} relative to the current working directory. +## The Octave commands are evaluated in a seperate context and any +## figures created while executing the script file are included in the +## report. All formatting syntax of @var{filename} is treated according +## to the specified output format and included in the report. +## +## Using @code{publish (@var{filename}, @var{output_format})} is +## equivalent to the function call using a structure +## +## @example +## @group +## @var{options}.format = @var{output_format}; +## publish (@var{filename}, @var{options}) +## @end group +## @end example +## +## which is described below. The same holds for using option-value-pairs +## +## @example +## @group +## @var{options}.@var{option1} = @var{value1}; +## publish (@var{filename}, @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 considered: +## +## @itemize @bullet +## @item +## @samp{format} --- Output format of the published script file, one of +## +## @samp{html} (default), @samp{doc}, @samp{latex}, @samp{ppt}, +## @samp{xml}, or @samp{pdf}. +## +## The output formats @samp{doc}, @samp{ppt}, and @samp{xml} are currently +## not supported. To generate a @samp{doc} report, open a generated +## @samp{html} report with your office suite. +## +## @item +## @samp{outputDir} --- Full path string of a directory, where the generated +## report will be located. If no directory is given, the report is generated +## in a subdirectory @samp{html} relative to the current working directory. +## +## @item +## @samp{stylesheet} --- Not supported, only for Matlab compatibility. +## +## @item +## @samp{createThumbnail} --- Not supported, only for Matlab compatibility. +## +## @item +## @samp{figureSnapMethod} --- Not supported, only for Matlab compatibility. +## +## @item +## @samp{imageFormat} --- Desired format for images produced, while +## evaluating the code. The allowed image formats depend on the output +## format: +## +## @itemize @bullet +## @item @samp{html} and @samp{xml} --- @samp{png} (default), any other +## image format supported by Octave +## +## @item @samp{latex} --- @samp{epsc2} (default), any other image format +## supported by Octave +## +## @item @samp{pdf} --- @samp{jpg} (default) or @samp{bmp}, note 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{maxHeight} and @samp{maxWidth} --- Maximum height (width) of the +## produced images in pixels. An empty value means no restriction. Both +## values have to be set, 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 code evaluation 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 shown output lines of the +## code evaluation +## +## @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 returned @var{output_file} is a string with the path and file name +## of the generated report. +## +## @seealso{grabcode} +## @end deftypefn + +function output_file = publish (file, varargin) + narginchk (1, Inf); + nargoutchk (0, 1); + + if (exist (file, "file") != 2) + error ("publish: FILE does not exist."); + endif + + ## Check file extension and for an Octave script + [~, file_name, file_ext] = fileparts (file); + file_info = __which__ (file_name); + + if ((! strcmp (file_ext, ".m")) || (! strcmp (file_info.type, "script"))) + error ("publish: Only Octave 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: Invalid second argument."); + endif + ## Call: publish (file, Name1, Value1, Name2, Value2, ...) + elseif ((rem (numel (varargin), 2) == 0) ... + && (all (cellfun (@ischar, varargin)))) + for i = 1:2:numel(varargin) + options = setfield (options, varargin{i}, varargin{i + 1}); + endfor + else + error ("publish: Invalid or inappropriate arguments."); + endif + + ## + ## Validate options struct + ## + + ## Options for the output + if (! isfield (options, "format")) + options.format = "html"; + else + options.format = validatestring (options.format, ... + {"html", "doc", "latex", "ppt", "xml", "pdf"}); + ## TODO: implement remaining formats + if (any (strcmp (options.format, {"doc", "ppt", "xml"}))) + error ("publish: Output format currently not supported"); + endif + endif + + if (! isfield (options, "outputDir")) + ## Matlab R2016a doc says default is "", but specifies to create a sub + ## directory named "html" in the current working directory. + options.outputDir = "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)) ... + || (! isbool (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"}); + ## TODO: implement + 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)) ... + || (uint64 (options.maxHeight) == 0)) + 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)) ... + || (uint64 (options.maxWidth) == 0)) + error ("publish: MAXWIDTH must be a positive integer"); + else + options.maxWidth = uint64 (options.maxWidth); + endif + + if (! isfield (options, "useNewFigure")) + options.useNewFigure = true; + elseif (! isbool (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)) || (! isbool (options.evalCode))) + error ("publish: EVALCODE must be TRUE or FALSE"); + endif + + if (!isfield (options, "catchError")) + options.catchError = true; + elseif ((! isscalar (options.catchError)) || (! isbool (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)) + 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)) || (! isbool (options.showCode))) + error ("publish: SHOWCODE must be TRUE or FALSE"); + endif + + doc_struct.title = ""; + doc_struct.intro = ""; + doc_struct.body = cell (); + doc_struct.m_source = deblank (read_file_to_cellstr (file)); + doc_struct.m_source_file_name = file; + + ## Split code and paragraphs, find formatting + doc_struct = parse_m_source (doc_struct); + + ## Create output directory + [mkdir_stat, mkdir_msg] = mkdir (options.outputDir); + if (mkdir_stat != 1) + error ("publish: cannot create output directory. %s", mkdir_msg); + endif + + if (options.evalCode) + doc_struct = eval_code (doc_struct, options); + endif + + output_file = create_output (doc_struct, options); +endfunction + + + +function doc_struct = parse_m_source (doc_struct) + ## PARSE_M_SOURCE First parsing level + ## This function extracts the overall structure (paragraphs and code + ## sections) given in doc_struct.m_source. + ## + ## The result is written to doc_struct.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_struct.body. + ## + + ## If there is nothing to parse + if (isempty (doc_struct.m_source)) + return; + endif + + ## Parsing helper functions + ## + ## Checks line to have N "%" or "#" lines + ## followed either by a space or is 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_struct.m_source) ... + | cellfun (is_no_break_head, doc_struct.m_source)); + + ## If the whole document is code + if (isempty (par_start_idx)) + doc_struct.body{end + 1}.type = "code"; + doc_struct.body{end}.content = strtrim (strjoin (... + doc_struct.m_source(1:length(doc_struct.m_source)), "\n")); + doc_struct.body{end}.lines = [1, length(doc_struct.m_source)]; + doc_struct.body{end}.output = {}; + return; + endif + + ## Determine continuous range of paragraphs + par_end_idx = [par_start_idx(2:end) - 1, length(doc_struct.m_source)]; + for i = 1:length(par_end_idx) + idx = find (! cellfun (is_paragraph, ... + doc_struct.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_struct.m_source)) + code_start_idx = [code_start_idx, par_end_idx(end) + 1]; + code_end_idx = [code_end_idx, length(doc_struct.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:length(code_start_idx) + if (all (cellfun (@(cstr) isempty (char (cstr)), ... + doc_struct.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_struct.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_struct.title = doc_struct.m_source{1}; + doc_struct.title = doc_struct.title(4:end); + content = doc_struct.m_source(2:par_end_idx(1)); + ## Strip leading "# " + content = cellfun(@(c) cellstr (c(3:end)), content); + doc_struct.intro = parse_paragraph_content (content); + title_offset = 1; + endif + + ## Add non-empty paragraphs and code to doc_struct + j = 1; + i = (1 + title_offset); + while ((i <= length(par_start_idx)) || (j <= length(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 <= length(code_start_idx)) + && ((i > length(par_start_idx)) + || (par_start_idx(i) > code_start_idx(j)))) + doc_struct.body{end + 1}.type = "code"; + lines = [code_start_idx(j), code_end_idx(j)]; + doc_struct.body{end}.content = strtrim (strjoin (... + doc_struct.m_source(lines(1):lines(2)), "\n")); + doc_struct.body{end}.lines = lines; + doc_struct.body{end}.output = {}; + j++; + endwhile + + if (i <= length(par_start_idx)) + type_str = "section"; + title_str = doc_struct.m_source{par_start_idx(i)}; + if (is_head (doc_struct.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_struct.body{end + 1}.type = type_str; + doc_struct.body{end}.content = title_str; + endif + + content = doc_struct.m_source(par_start_idx(i) + 1:par_end_idx(i)); + ## Strip leading "# " + content = cellfun(@(c) cellstr (c(3:end)), content); + doc_struct.body = [doc_struct.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_struct.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 + + ## Split into blocks seperated 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"; + p_content{end}.content = strjoin (block, "\n"); + ## Revove first "* " + p_content{end}.content = p_content{end}.content(3:end); + ## Split items + p_content{end}.content = strsplit (p_content{end}.content, "\n* "); + continue; + endif + + ## Numbered list starts with "# " + if (strncmp (block{1}, "# ", 2)) + p_content{end+1}.type = "numbered_list"; + p_content{end}.content = strjoin (block, "\n"); + ## Revove first "# " + p_content{end}.content = p_content{end}.content(3:end); + ## Split items + p_content{end}.content = strsplit (p_content{end}.content, "\n# "); + continue; + endif + + ## Include fname.m + if (! isempty ([~,~,~,~,fname] = regexpi (strjoin (block, ""), ... + '^(.*)<\/include>$'))) + ## 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 <> + if (! isempty ([~,~,~,~,fname] = regexpi (strjoin (block, ""), ... + '^<<(.*)>>$'))) + p_content{end+1}.type = "graphic"; + p_content{end}.content = strtrim ((fname{1}){1}); + continue; + endif + + ## Parse remaining blocks line by line + j = 1; + while (j <= length(block)) + ## HTML markup + if (strcmpi (block{j}, "")) + start_html = j + 1; + while ((j < length(block)) && ! strcmpi (block{j}, "")) + j++; + endwhile + if ((j == length(block)) && ! strcmpi (block{j}, "")) + warning ("publish: no closing found"); + else + j++; ## Skip closing tag + endif + if (j > start_html) + p_content{end+1}.type = "html"; + p_content{end}.content = strjoin (block(start_html:j-2), "\n"); + endif + ## LaTeX markup + elseif (strcmpi (block{j}, "")) + start_latex = j + 1; + while ((j < length(block)) && ! strcmpi (block{j}, "")) + j++; + endwhile + if ((j == length(block)) && ! strcmpi (block{j}, "")) + warning ("publish: no closing found"); + else + j++; ## Skrip closing tag + endif + if (j > start_latex) + p_content{end+1}.type = "latex"; + p_content{end}.content = strjoin (block(start_latex:j-2), "\n"); + endif + ## Remaining 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 + ## + else + if ((j == 1) || isempty (p_content) ... + || ! strcmp (p_content{end}.type, "text")) + p_content{end+1}.type = "text"; + p_content{end}.content = block{j}; + else + p_content{end}.content = strjoin ({p_content{end}.content, ... + block{j}}, "\n"); + endif + j++; + endif + endwhile + endfor +endfunction + + + +function m_source = read_file_to_cellstr (file) + ## READ_FILE_TO_CELLSTR reads a given file line by line to a cellstring + fid = fopen (file, "r"); + i = 1; + m_source{i} = fgetl (fid); + while (ischar (m_source{i})) + i++; + m_source{i} = fgetl (fid); + endwhile + fclose(fid); + m_source = cellstr (m_source(1:end-1)); ## No EOL +endfunction + + + +function ofile = create_output (doc_struct, options) + ## CREATE_OUTPUT creates the desired output file + ## + + formatter = []; + ofile_ext = ""; + switch (options.format) + case "html" + formatter = @__publish_html_output__; + ofile_ext = ".html"; + case {"latex", "pdf"} + formatter = @__publish_latex_output__; + ofile_ext = ".tex"; + endswitch + + ## Use title, or if not given the m-file name + title_str = doc_struct.title; + if (isempty (title_str)) + [~,title_str] = fileparts (doc_struct.m_source_file_name); + endif + + content = formatter ("header", title_str, ... + format_output (doc_struct.intro, formatter, options), ... + get_toc (doc_struct.body)); + content = [content, format_output(doc_struct.body, formatter, options)]; + content = [content, formatter("footer", strjoin (doc_struct.m_source, "\n"))]; + + ## Write file + [~,ofile] = fileparts (doc_struct.m_source_file_name); + ofile_name = [ofile, ofile_ext]; + ofile = [options.outputDir, filesep(), 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 + system (["cd ", options.outputDir," && pdflatex ", ofile_name]); + endfor + endif + endif +endfunction + + + +function toc_cstr = get_toc (cstr) + ## GET_TOC extracts the table of contents from a cellstring (e.g. + ## doc_struct.body) with each section headline as a cell in a returned + ## cellstring. + ## + toc_cstr = cell (); + for i = 1:length(cstr) + if (strcmp (cstr{i}.type, "section")) + toc_cstr{end + 1} = cstr{i}.content; + endif + endfor +endfunction + + + +function str = format_output (cstr, formatter, options) + ## FORMAT_OUTPUT steps through all blocks (doc_struct.intro or + ## doc_struct.body) in cstr and produces a single result string + ## with the source code in the desired output format. + ## + ## formatter has the only knowlegde how to enforce the target format + ## and produces for each block the necessary target format source string. + ## + + str = ""; + for i = 1:length(cstr) + switch (cstr{i}.type) + case "code" + if (options.showCode) + str = [str, formatter(cstr{i}.type, cstr{i}.content)]; + endif + if (options.evalCode) + str = [str, formatter("code_output", cstr{i}.output)]; + endif + case "text" + 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, bold, italic, monospaced, (TM), (R) + ## + + ## Links "" + str = regexprep (str, '<(\S{3,}[^\s<>]*)>', ... + formatter ("link", "$1", "$1")); + ## Links "" + ## TODO: better pointer to the function documentation + str = regexprep (str, ']*) *([^<>$]*)>', ... + formatter ("link", ["https://www.gnu.org/software/octave/", ... + "doc/interpreter/Function-Index.html"], "$2")); + ## Links "" + str = regexprep (str, '<(\S{3,}[^\s<>]*) *([^<>$]*)>', ... + formatter ("link", "$1", "$2")); + oldstr = str; + ## Loop because of inlined expressions, e.g. *BOLD _ITALIC_* + do + oldstr = str; + ## Bold + str = regexprep (str, '\*([^*$_|]*)\*', formatter ("bold", "$1")); + ## Italic + str = regexprep (str, '_([^_$|*]*)_', formatter ("italic", "$1")); + ## Monospaced + str = regexprep (str, '\|([^|$_*]*)\|', formatter ("monospaced", "$1")); + until (strcmp (str, oldstr)) + ## Replace special symbols + str = strrep (str, "(TM)", formatter("TM")); + str = strrep (str, "(R)", formatter("R")); +endfunction + + + +function doc_struct = eval_code (doc_struct, options) + ## EVAL_CODE Thrid level parsing + ## + ## Generates the output of the script code and takes care of generated + ## figures. + ## + + ## Neccessary as the code does not run interactively + page_screen_output (0, "local"); + + ## Remember previously opened figures + fig_ids = findall (0, "type", "figure"); + [~,fig_name] = fileparts (doc_struct.m_source_file_name); + fig_num = 1; + fig_list = struct (); + + ## mat-file used as temporary context + tmp_context = [tempname(), ".mat"]; + + ## 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:length(doc_struct.body) + if (strcmp (doc_struct.body{i}.type, "code")) + r = doc_struct.body{i}.lines; + code_str = strjoin (doc_struct.m_source(r(1):r(2)), "\n"); + if (options.catchError) + try + doc_struct.body{i}.output = eval_code_helper (tmp_context, code_str); + catch err + doc_struct.body{i}.output = cellstr (["error: ", err.message, ... + "\n\tin:\n\n", code_str]); + end_try_catch + else + doc_struct.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:length(fig_ids_new) + drawnow (); + if (isempty (get (fig_ids_new(j), "children"))) + continue; + endif + fname = [fig_name, "-", num2str(fig_num), "."]; + if (strncmp (options.imageFormat, "eps", 3)) + fname = [fname, "eps"]; + else + fname = [fname, options.imageFormat]; + endif + print_opts = {fig_ids_new(j), ... + [options.outputDir, filesep(), fname], ... + ["-d", options.imageFormat], "-color"}; + if (! isempty (options.maxWidth) && ! isempty (options.maxHeight)) + print_opts{end + 1} = ["-S", num2str(options.maxWidth), ... + num2str(options.maxHeight)]; + elseif (! isempty (options.maxWidth) || ! isempty (options.maxWidth)) + warning (["publish: specify both options.maxWidth ", ... + "and options.maxWidth"]); + 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, num2str (i)), fig_elem]; + endif + fig_list = setfield (fig_list, num2str (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_struct.body{i}.output)) + doc_struct.body{i}.output = ... + doc_struct.body{i}.output(1:options.maxOutputLines); + endif + doc_struct.body{i}.output = strjoin (doc_struct.body{i}.output, "\n"); + endif + endfor + + ## Close any by publish opened figures + 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:length(fig_code_blocks) + elems = getfield (fig_list, fig_code_blocks{i}); + ## Compute index, where the figure(s) has to be inserterd + j = str2num (fig_code_blocks{i}) + body_offset; + doc_struct.body = [doc_struct.body(1:j), elems, doc_struct.body(j+1:end)]; + body_offset = body_offset + length (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 + + ## TODO: potential conflicting variables sourrounded by "___". Maybe there + ## is a better solution. + if (isempty (___code___)) + return; + endif + + if (exist (___context___, "file") == 2) + load (___context___); + endif + + ___cstr___ = strsplit (evalc (___code___), "\n"); + + clear ___code___ + save (___context___); +endfunction + + + +## Bad function calls + +%!error publish () +%!error publish (1) +%!error publish ("non_existing_file.m") +%!error publish ("publish.m") +%!error publish ("test_script.m", "format", "html", "showCode") +%!error [str1, str2] = publish ("test_script.m") diff -r 03764eef9f7c -r b6f482e29afd scripts/help/__unimplemented__.m --- a/scripts/help/__unimplemented__.m Thu Jul 14 19:56:44 2016 -0400 +++ b/scripts/help/__unimplemented__.m Fri Jul 15 11:46:16 2016 +0200 @@ -656,7 +656,6 @@ "getpixelposition", "getReport", "gobjects", - "grabcode", "graph", "graymon", "griddedInterpolant", @@ -785,7 +784,6 @@ "properties", "propertyeditor", "psi", - "publish", "quad2d", "RandStream", "rbbox", diff -r 03764eef9f7c -r b6f482e29afd test/module.mk --- a/test/module.mk Thu Jul 14 19:56:44 2016 -0400 +++ b/test/module.mk Fri Jul 15 11:46:16 2016 +0200 @@ -54,6 +54,7 @@ include test/ctor-vs-method/module.mk include test/fcn-handle-derived-resolution/module.mk include test/nest/module.mk +include test/publish/module.mk ALL_LOCAL_TARGETS += test/.gdbinit diff -r 03764eef9f7c -r b6f482e29afd test/publish/module.mk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/publish/module.mk Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,8 @@ +publish_TEST_FILES = \ + test/publish/test_script_code_only.m \ + test/publish/test_script_empty.m \ + test/publish/test_script_example.m \ + test/publish/test_script_head_only.m \ + test/publish/test_script.m + +TEST_FILES += $(publish_TEST_FILES) diff -r 03764eef9f7c -r b6f482e29afd test/publish/publish.tst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/publish/publish.tst Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,23 @@ +## publish + +%!testif HAVE_X_WINDOWS +%! cases = dir ("test_script*.m"); +%! cases = strsplit (strrep ([cases.name], ".m", ".m\n")); +%! for i = 1:length(cases)-1 +%! publish (cases{i}); +%! endfor +%! confirm_recursive_rmdir (false, "local"); +%! rmdir ("html", "s"); + +## grabcode + +%!testif HAVE_X_WINDOWS +%! publish ("test_script.m"); +%! str1 = fileread ("test_script.m"); +%! str2 = grabcode ("html/test_script.html"); +%! confirm_recursive_rmdir (false, "local"); +%! rmdir ("html", "s"); +%! # Canonicalize strings +%! str1 = strjoin (deblank (strsplit (str1, "\n")), "\n"); +%! str2 = strjoin (deblank (strsplit (str2, "\n")), "\n"); +%! assert (hash ("md5", str1), hash ("md5", str2)); \ No newline at end of file diff -r 03764eef9f7c -r b6f482e29afd test/publish/test_script.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/publish/test_script.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,322 @@ +%% Headline +% Headline description with a link +% +% +% Spanning some lines and blanks. +% + +%% + +disp ("First recognized Octave code after %%") + +%% SECTION TITLE +% DESCRIPTIVE TEXT + +%%% SECTION TITLE WITHOUT SECTION BREAK +% For Matlab compatibility + +## SECTION TITLE +# DESCRIPTIVE TEXT + +### SECTION TITLE WITHOUT SECTION BREAK +# Should not work in Octave style +# and should be interpreted as usual Octave code + +%% +% + +## +# + +% some real comment +i = 0:2*pi + +# some real comment +y = sin(i) + +%% +% +% Content without head. +% + +% some real comment and split code block +x = 0:2*pi + +# some real comment and split code block +y = sin(x) + +%% +% + +% reusing old values +y = cos(i) + +# some real comment and split code block +y = cos(x) + +%% Text formatting +% PLAIN TEXT _ITALIC TEXT_ *BOLD TEXT* |MONOSPACED TEXT| +% |MONOSPACED TEXT| PLAIN TEXT _ITALIC TEXT_ *BOLD TEXT* +% *BOLD TEXT* |MONOSPACED TEXT| PLAIN TEXT _ITALIC TEXT_ +% _ITALIC TEXT_ *BOLD TEXT* |MONOSPACED TEXT| PLAIN TEXT +% Trademarks: +% TEXT(TM) +% TEXT(R) +% +% Good inlining shoud work +% *BOLD _ITALIC |MONOSPACED| TEXT_* +% _ITALIC *BOLD |MONOSPACED| TEXT*_ +% |MONOSPACED *BOLD _ITALIC_ TEXT*| +% +% Bad inlining should not work |MONOSPACED *BOLD TEXT|* + +% figure code +plot (x,y) + +% another plot +figure () +plot (y,x) + +## Text formatting +# PLAIN TEXT _ITALIC TEXT_ *BOLD TEXT* |MONOSPACED TEXT| +# |MONOSPACED TEXT| PLAIN TEXT _ITALIC TEXT_ *BOLD TEXT* +# *BOLD TEXT* |MONOSPACED TEXT| PLAIN TEXT _ITALIC TEXT_ +# _ITALIC TEXT_ *BOLD TEXT* |MONOSPACED TEXT| PLAIN TEXT +# Trademarks: +# TEXT(TM) +# TEXT(R) +# +# Good inlining shoud work +# *BOLD _ITALIC |MONOSPACED| TEXT_* +# _ITALIC *BOLD |MONOSPACED| TEXT*_ +# |MONOSPACED *BOLD _ITALIC_ TEXT*| +# +# Bad inlining should not work |MONOSPACED *BOLD TEXT|* + +% again another plot +plot (x,y) + +%% Bulleted List +% +% * BULLETED ITEM 1 +% * BULLETED ITEM 2 +% * BULLETED ITEM 3 *BOLD* +% * BULLETED ITEM 4 +% + +## Bulleted List +# +# * BULLETED ITEM 1 +# * BULLETED ITEM 2 +# * BULLETED ITEM 3 *BOLD* +# * BULLETED ITEM 4 +# + +%% Numbered List +% +% # NUMBERED ITEM 1 +% # NUMBERED ITEM 2 +% # NUMBERED ITEM 3 *BOLD* +% # NUMBERED ITEM 4 +% + +## Numbered List +# +# # NUMBERED ITEM 1 +# # NUMBERED ITEM 2 +# # NUMBERED ITEM 3 *BOLD* +# # NUMBERED ITEM 4 +# + +%% +% +% PREFORMATTED +% TEXT +% + +## +# +# PREFORMATTED +# TEXT +# + +%% GNU Octave Code +% +% for i = 1:10 +% disp (x) +% endfor +% + +## GNU Octave Code +# +# for i = 1:10 +# disp (x) +# endfor +# + +%% External File Content +% +% test_script_code_only.m +% + +## External File Content +# +# test_script_code_only.m +# + +%% External Graphic +% +% <> +% + +## External Graphic +# +# <> +# + +%% Inline LaTeX +% $f(n) = n^5 + 4n^2 + 2 |_{n=17}$ + +## Inline LaTeX +# $f(n) = n^5 + 4n^2 + 2 |_{n=17}$ + +%% Block LaTeX +% $$f(n) = n^5 + 4n^2 + 2 |_{n=17}$$ + +## Block LaTeX +# $$f(n) = n^5 + 4n^2 + 2 |_{n=17}$$ + +%% Links +% +% +% +% +% +% + +## Links +# +# +# +# +# +# + +%% HTML Markup +% +%
12
33
+% +%
onetwo
+% +% + +## HTML Markup +# +# +# +# +# +# +#
onetwo
+# +# + +%% LaTeX Markup +% +% \begin{equation} +% \begin{pmatrix} +% 1 & 2 \\ 3 & 4 +% \end{pmatrix} +% \end{equation} +% +% + +## LaTeX Markup +# +# \begin{equation} +# \begin{pmatrix} +# 1 & 2 \\ 3 & 4 +# \end{pmatrix} +# \end{equation} +# +# + +%% Long void +% +% +% +% +% +% +% +% +% content +% +% +% +% +% +% +% + +%% +% +% +% +% +% +% +% +% +% and continued +% +% +% +% +% +% +% + +## Long void +# +# +# +# +# +# +# +# content +# +# +# +# +# +# +# +# +# +# + +## +# +# +# +# +# +# +# +# and continued +# +# +# +# +# +# +# +# +# +# diff -r 03764eef9f7c -r b6f482e29afd test/publish/test_script_code_only.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/publish/test_script_code_only.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,24 @@ + +% Code only with a very very very very very very very very very very looong line +% +x = 5; + +for i = 1:5 + x += i; # Might be useful "perhaps" +endfor + +%{ +Multiline comment with keyword if "if" and 'if' +%} + +if (x == 'a') + y = sin (x); +endif + +#{ +Multiline comment with keyword if "if" and 'if' +#} + +str = "some % string \" ' with %{"; +str2 = 'another % string '' " with %{'; +% \ No newline at end of file diff -r 03764eef9f7c -r b6f482e29afd test/publish/test_script_empty.m diff -r 03764eef9f7c -r b6f482e29afd test/publish/test_script_example.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/publish/test_script_example.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,16 @@ +%% Headline title +% +% Some *bold*, _italic_, or |monospaced| Text with +% a . +%% + +# "Real" Octave commands to be evaluated +sombrero () + +## Octave comment style supported as well +# +# * Bulleted list item 1 +# * Bulleted list item 2 +# +# # Numbered list item 1 +# # Numbered list item 2 \ No newline at end of file diff -r 03764eef9f7c -r b6f482e29afd test/publish/test_script_head_only.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/publish/test_script_head_only.m Fri Jul 15 11:46:16 2016 +0200 @@ -0,0 +1,4 @@ +%% Headline +% Headline description. +% about some lines no blanks +%% \ No newline at end of file