Mercurial > octave
changeset 22705:e9a0aa0a49ed
Overhaul publish function and it's private helper functions.
* publish.m: Rewrite docstring. Don't use narginchk, nargoutchk. Don't end
error messages with a period. Fix input validation to check that PROPERTY_NAME
in a propert/value pair is a string, but value does not need to be.
Rewrite messages in error() function calls. Use single quotes to reduce
excessive backslashing. Don't require TRUE/FALSE options to be boolean, just
real. Remove excessive parentheses around tests in if conditionals.
Rename doc_struct to just doc for simplicity. Prefer numel() to length().
Correct cuddling of parentheses to indicate indexing versus function call.
Use do/until loop rather than while loop to simplify loop setup.
Use str2double rather than str2num.
__publish_html_output__.m: Use sprintf() to create HERE documents to make
definition of large constant blocks of text easier to understand. Prefer
numel() over length(). Rename all handle_XXX functions to do_XXX to follow
Octave convention. Use single quoted strings to avoid excessive backslashing.
Add newlines to lists so that generated HTML is human-readable. Use in-place
operators for efficiency. Use sprintf rather than num2str for efficiency.
Use direct comparison when looking at a single character rather than strcmp for
efficiency. Use single regexprep with alternation rather than for loop with
individual patterns for efficiency.
* __publish_latex_output__.m: Use sprintf() to create HERE documents to make
definition of large constant blocks of text easier to understand. Prefer
numel() over length(). Rename all handle_XXX functions to do_XXX to follow
Octave convention. Use single quoted strings to avoid excessive backslashing.
author | Rik <rik@octave.org> |
---|---|
date | Mon, 31 Oct 2016 22:33:00 -0700 |
parents | b5407b1ab11a |
children | 5a5d6c8647f6 |
files | scripts/general/private/__publish_html_output__.m scripts/general/private/__publish_latex_output__.m scripts/general/publish.m |
diffstat | 3 files changed, 631 insertions(+), 607 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/general/private/__publish_html_output__.m Mon Oct 31 12:22:43 2016 -0700 +++ b/scripts/general/private/__publish_html_output__.m Mon Oct 31 22:33:00 2016 -0700 @@ -1,315 +1,332 @@ -function outstr = __publish_html_output__ (varargin) - ## - ## Types to handle are: +function outstr = __publish_html_output__ (type, varargin) + ## Recognized types 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});"]); + ## "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" () + + outstr = feval (["do_" type], varargin{:}); endfunction -function outstr = handle_header (title_str, intro_str, toc_cstr) - mathjax_str = ["<script type=\"text/x-mathjax-config\">\n", ... - "MathJax.Hub.Config({\n", ... - " tex2jax: { inlineMath: [['$','$'], ['\\\\(','\\\\)']] },\n", ... - " TeX: { equationNumbers: { autoNumber: 'all' } }\n", ... - "});\n", ... - "</script>\n", ... - "<script type=\"text/javascript\" async ", ... - "src=\"https://cdn.mathjax.org/mathjax/latest/MathJax.js?", ... - "config=TeX-MML-AM_CHTML\"></script>\n"]; - stylesheet_str = ["<style>\n", ... - "body > * {\n", ... - " max-width: 42em;\n", ... - "}\n", ... - "body {\n", ... - " font-family: \"Roboto Condensed\", sans-serif;\n", ... - " padding-left: 7.5em;\n", ... - " padding-right: 7.5em;\n", ... - "}\n", ... - "pre, code {\n", ... - " max-width: 50em;\n", ... - " font-family: monospace;\n", ... - "}\n", ... - "pre.oct-code {\n", ... - " border: 1px solid Grey;\n", ... - " padding: 5px;\n", ... - "}\n", ... - "pre.oct-code-output {\n", ... - " margin-left: 2em;\n", ... - "}\n", ... - "span.comment {\n", ... - " color: ForestGreen;\n", ... - "}\n",... - "span.keyword {\n", ... - " color: Blue;\n", ... - "}\n",... - "span.string {\n", ... - " color: DarkOrchid;\n", ... - "}\n",... - "footer {\n", ... - " margin-top: 2em;\n", ... - " font-size: 80%;\n", ... - "}\n", ... - "a, a:visited {\n", ... - " color: Blue;\n", ... - "}\n", ... - "h2 {\n", ... - " font-family: \"Roboto Condensed\", serif;\n", ... - " margin-top: 1.5em;\n", ... - "}\n", ... - "h2 a, h2 a:visited {\n", ... - " color: Black;\n", ... - "}\n", ... - "</style>\n"]; - outstr = ["<!DOCTYPE html>\n", ... - "<html>\n", ... - "<head>\n", ... - "<meta charset=\"UTF-8\">\n", ... - "<title>", title_str, "</title>\n", ... - mathjax_str, ... - stylesheet_str, ... - "</head>\n", ... - "<body>\n", ... - "<h1>", title_str, "</h1>\n", ... - intro_str]; + +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://cdn.mathjax.org/mathjax/latest/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:length(toc_cstr) - toc_cstr{i} = handle_link (["#node", num2str(i)], toc_cstr{i}); + for i = 1:numel (toc_cstr) + toc_cstr{i} = do_link (["#node" sprintf("%d", i)], toc_cstr{i}); endfor - outstr = [outstr, "<h2>Contents</h2>", ... - handle_bulleted_list(toc_cstr)]; + outstr = [outstr, "<h2>Contents</h2>", do_bulleted_list(toc_cstr)]; endif ## Reset section counter - handle_section (); + do_section (); + endfunction -function outstr = handle_footer (m_source_str) - outstr = ["\n", ... - "<footer><hr>", ... - "<a href=\"http://www.octave.org\">Published with GNU Octave ", ... - version(), "</a></footer>\n", ... - "<!--\n", ... - "##### SOURCE BEGIN #####\n", ... - m_source_str, ... - "\n##### SOURCE END #####\n", ... - "-->\n", ... - "</body>\n", ... - "</html>\n"]; +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 = handle_code (str) - outstr = ["<pre class=\"oct-code\">", syntax_highlight(str), "</pre>"]; +function outstr = do_code (str) + outstr = ["\n", '<pre class="oct-code">' syntax_highlight(str) "</pre>\n"]; endfunction -function outstr = handle_code_output (str) - outstr = ["<pre class=\"oct-code-output\">", str, "</pre>"]; +function outstr = do_code_output (str) + outstr = ["\n", '<pre class="oct-code-output">' str "</pre>\n"]; endfunction -function outstr = handle_section (varargin) +function outstr = do_section (varargin) persistent counter = 1; + if (nargin == 0) + outstr = ""; counter = 1; - outstr = ""; return; endif - outstr = ["<h2><a id=\"node", num2str(counter), "\">", varargin{1}, ... - "</a></h2>"]; + + outstr = ['<h2><a id="node' sprintf("%d", counter) '">', ... + varargin{1}, ... + "</a></h2>"]; + counter++; + endfunction -function outstr = handle_preformatted_code (str) - outstr = ["<pre class=\"pre-code\">", syntax_highlight(str), "</pre>"]; +function outstr = do_preformatted_code (str) + outstr = ["\n", '<pre class="pre-code">' syntax_highlight(str) "</pre>\n"]; endfunction -function outstr = handle_preformatted_text (str) - outstr = ["<pre class=\"pre-text\">", str, "</pre>"]; +function outstr = do_preformatted_text (str) + outstr = ["\n", '<pre class="pre-text">' str "</pre>\n"]; endfunction -function outstr = handle_bulleted_list (cstr) - outstr = "<ul>"; - for i = 1:length(cstr) - outstr = [outstr, "<li>", cstr{i}, "</li>"]; +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>"]; + outstr = [outstr, "</ul>\n"]; endfunction -function outstr = handle_numbered_list (cstr) - outstr = "<ol>"; - for i = 1:length(cstr) - outstr = [outstr, "<li>", cstr{i}, "</li>"]; +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>"]; + outstr = [outstr, "</ol>\n"]; endfunction -function outstr = handle_graphic (str) - outstr = ["<img src=\"", str,"\" alt=\"", str, "\">"]; +function outstr = do_graphic (str) + outstr = ['<img src="' str '" alt="' str '">']; endfunction -function outstr = handle_html (str) +function outstr = do_html (str) outstr = str; endfunction -function outstr = handle_latex (str) +function outstr = do_latex (str) outstr = ""; endfunction -function outstr = handle_link (url_str, str) - outstr = ["<a href=\"", url_str,"\">", str, "</a>"]; +function outstr = do_link (url_str, str) + outstr = ['<a href="' url_str '">' str "</a>"]; endfunction -function outstr = handle_text (str) - outstr = ["<p>", str, "</p>"]; +function outstr = do_text (str) + outstr = ["\n<p>" str "</p>\n"]; endfunction -function outstr = handle_bold (str) - outstr = ["<b>", str, "</b>"]; +function outstr = do_bold (str) + outstr = ["<b>" str "</b>"]; endfunction -function outstr = handle_italic (str) - outstr = ["<i>", str, "</i>"]; +function outstr = do_italic (str) + outstr = ["<i>" str "</i>"]; endfunction -function outstr = handle_monospaced (str) - outstr = ["<code>", str, "</code>"]; +function outstr = do_monospaced (str) + outstr = ["<code>" str "</code>"]; endfunction -function outstr = handle_TM () +function outstr = do_TM () outstr = "™"; endfunction -function outstr = handle_R () +function outstr = do_R () outstr = "®"; 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) - ## SYNTAX_HIGHLIGHT a primitive parser to add syntax highlight via <span> - ## tags. Should be replaced by a better solution. - ## + outstr = ""; + placeholder_cstr = {}; + i = 1; + plh = 0; - outstr = ""; - i = 1; - placeholder_cstr = {}; - plh = 0; - while (i <= length(str)) + while (i <= numel (str)) ## Block comment if (any (strncmp (str(i:end), {"%{", "#{"}, 2))) - plh_str = ["<span class=\"comment\">", str(i:i+1)]; - i = i + 2; - while ((i <= length(str)) ... + 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++; + i += 1; endwhile - if (i < length(str)) + if (i < numel (str)) plh_str = [plh_str, str(i:i+1), "</span>"]; - i = i + 2; + i += 2; else plh_str = [plh_str, "</span>"]; endif - plh = plh + 1; + plh += 1; placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", num2str(plh), " "]; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; ## Line comment - elseif (any (strcmp (str(i), {"%", "#"}))) - plh_str = "<span class=\"comment\">"; - while ((i <= length(str)) && (! strcmp (str(i), "\n"))) - plh_str = [plh_str, str(i)]; - i++; - endwhile + 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"]; - i++; - plh = plh + 1; + plh += 1; placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", num2str(plh), " "]; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; ## Single quoted string - elseif (strcmp (str(i), "'")) + elseif (str(i) == "'") plh_str = "<span class=\"string\">'"; - i = i + 1; - while (i <= length(str)) + i += 1; + while (i <= numel (str)) ## Ignore escaped string terminations if (strncmp (str(i:end), "''", 2)) plh_str = [plh_str, "''"]; - i = i + 2; - ## Is string termination - elseif (strcmp (str(i), "'")) + i += 2; + ## Is char a string termination? + elseif (str(i) == "'") plh_str = [plh_str, "'"]; - i = i + 1; + i += 1; break; - ## Is string termination by line break - elseif (strcmp (str(i), "\n")) + ## Is string terminated by line break? + elseif (str(i) == "\n") break; ## String content else plh_str = [plh_str, str(i)]; - i = i + 1; + i += 1; endif endwhile plh_str = [plh_str, "</span>"]; - plh = plh + 1; + plh += 1; placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", num2str(plh), " "]; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; ## Double quoted string - elseif (strcmp (str(i), "\"")) - plh_str = "<span class=\"string\">\""; - i = i + 1; - while (i <= length(str)) - ## Is string termination - if (strcmp (str(i), "\"") && ! strcmp (str(i - 1), "\\")) - plh_str = [plh_str, "\""]; - i = i + 1; + 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 termination by line break - elseif (strcmp (str(i), "\n")) + ## Is string terminated by line break? + elseif (str(i) == "\n") break; ## String content else plh_str = [plh_str, str(i)]; - i = i + 1; + i += 1; endif endwhile plh_str = [plh_str, "</span>"]; - plh = plh + 1; + plh += 1; placeholder_cstr{plh} = plh_str; - outstr = [outstr, " PUBLISHPLACEHOLDER", num2str(plh), " "]; + outstr = [outstr, " PUBLISHPLACEHOLDER", sprintf("%d", plh), " "]; else outstr = [outstr, str(i)]; - i++; + i += 1; endif endwhile - kwords = iskeyword (); - ## TODO: remove hack for regexp (bug #38149) + + persistent kword_ptn = strjoin (iskeyword (), '|'); + + ## FIXME: remove hack for regexprep once bug #38149 is solved outstr = [" ", strrep(outstr, "\n", " \n "), " "]; - for i = 1:length(kwords) - outstr = regexprep (outstr, ... - ['(\s)(', kwords{i},')(\s|\()'], ... - ["$1<span class=\"keyword\">$2</span>$3"]); - endfor - ## TODO: remove hack for regexp (bug #38149) - outstr = strrep(outstr(2:end-1), " \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", num2str(i), " "], ... - placeholder_cstr{i}); + outstr = strrep (outstr, [" PUBLISHPLACEHOLDER", sprintf("%d", i), " "], + placeholder_cstr{i}); endfor -endfunction \ No newline at end of file + +endfunction +
--- a/scripts/general/private/__publish_latex_output__.m Mon Oct 31 12:22:43 2016 -0700 +++ b/scripts/general/private/__publish_latex_output__.m Mon Oct 31 22:33:00 2016 -0700 @@ -1,6 +1,5 @@ -function outstr = __publish_latex_output__ (varargin) - ## - ## Types to handle are: +function outstr = __publish_latex_output__ (type, varargin) + ## Recognized types are: ## ## * "header" (title_str, intro_str, toc_cstr) ## * "footer" () @@ -21,146 +20,169 @@ ## * "link" (url_str, url_str, str) ## * "TM" () ## * "R" () - ## - eval (["outstr = handle_", varargin{1}, " (varargin{2:end});"]); + + outstr = feval (["do_" type], varargin{:}); 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"]; +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 = ["\n\n", ... - "\\documentclass[10pt]{article}\n", ... - "\\usepackage{listings}\n", ... - "\\usepackage{mathtools}\n", ... - "\\usepackage{amssymb}\n", ... - "\\usepackage{graphicx}\n", ... - "\\usepackage{hyperref}\n", ... - "\\usepackage{xcolor}\n", ... - "\\usepackage{titlesec}\n", ... - "\\usepackage[utf8]{inputenc}\n", ... - "\\usepackage[T1]{fontenc}\n", ... - "\\usepackage{lmodern}\n"]; + 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 = ["\n\n", ... - "\\lstset{\n", ... - "language=Octave,\n", ... - "numbers=none,\n", ... - "frame=single,\n", ... - "tabsize=2,\n", ... - "showstringspaces=false,\n", ... - "breaklines=true}\n"]; + listings_option = sprintf ("%s\n", +"", +"", +'\lstset{', +'language=Octave,', +'numbers=none,', +'frame=single,', +'tabsize=2,', +'showstringspaces=false,', +'breaklines=true}'); - 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_latex(title_str),"}}\n\n", ... - "\\tableofcontents\n", ... - "\\vspace*{4em}\n\n"]; + 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*{' escape_latex(title_str) '}}'], +"", +'\tableofcontents', +'\vspace*{4em}', +""); 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"]; +function outstr = do_footer (m_source_str) + outstr = ["\n\n" '\end{document}' "\n"]; endfunction -function outstr = handle_code_output (str) - outstr = ["\\begin{lstlisting}", ... - "[language={},xleftmargin=5pt,frame=none]\n", ... - str, "\n\\end{lstlisting}\n"]; +function outstr = do_code (str) + outstr = ['\begin{lstlisting}' "\n", str, "\n" '\end{lstlisting}' "\n"]; endfunction -function outstr = handle_section (str) - outstr = ["\n\n\\phantomsection\n", ... - "\\addcontentsline{toc}{section}{", escape_latex(str), "}\n", ... - "\\subsection*{", escape_latex(str), "}\n\n"]; +function outstr = do_code_output (str) + outstr = sprintf ("%s\n", +'\begin{lstlisting}[language={},xleftmargin=5pt,frame=none]', +str, +'\end{lstlisting}'); endfunction -function outstr = handle_preformatted_code (str) - outstr = ["\\begin{lstlisting}\n", str, "\n\\end{lstlisting}\n"]; +function outstr = do_section (str) + outstr = sprintf ("%s\n", +"", +"", +'\phantomsection', +['\addcontentsline{toc}{section}{' escape_latex(str) '}'], +['\subsection*{' escape_latex(str) '}'], +""); endfunction -function outstr = handle_preformatted_text (str) - outstr = ["\\begin{lstlisting}[language={}]\n", ... - str, "\n\\end{lstlisting}\n"]; +function outstr = do_preformatted_code (str) + outstr = sprintf ("%s\n", +'\begin{lstlisting}', +str, +'\end{lstlisting}'); endfunction -function outstr = handle_bulleted_list (cstr) - outstr = "\n\\begin{itemize}\n"; - for i = 1:length(cstr) - outstr = [outstr, "\\item ", escape_latex(cstr{i}), "\n"]; - endfor - outstr = [outstr, "\\end{itemize}\n"]; +function outstr = do_preformatted_text (str) + outstr = sprintf ("%s\n", +'\begin{lstlisting}[language={}]', +str, +'\end{lstlisting}'); endfunction -function outstr = handle_numbered_list (cstr) - outstr = "\n\\begin{enumerate}\n"; - for i = 1:length(cstr) - outstr = [outstr, "\\item ", escape_latex(cstr{i}), "\n"]; +function outstr = do_bulleted_list (cstr) + outstr = ["\n" '\begin{itemize}' "\n"]; + for i = 1:numel (cstr) + outstr = [outstr, '\item ' escape_latex(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 ' escape_latex(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"]; +function outstr = do_graphic (str) + outstr = sprintf ("%s\n", +'\begin{figure}[!ht]', +['\includegraphics[width=\textwidth]{' str '}'], +'\end{figure}'); endfunction -function outstr = handle_html (str) +function outstr = do_html (str) outstr = ""; endfunction -function outstr = handle_latex (str) +function outstr = do_latex (str) outstr = str; endfunction -function outstr = handle_link (url_str, str) - outstr = ["\\href{", url_str,"}{", str, "}"]; +function outstr = do_link (url_str, str) + outstr = ['\href{' url_str '}{' str '}']; endfunction -function outstr = handle_text (str) - outstr = ["\n\n", escape_latex(str), "\n\n"]; +function outstr = do_text (str) + outstr = ["\n\n" escape_latex(str) "\n\n"]; endfunction -function outstr = handle_bold (str) - outstr = ["\\textbf{", str, "}"]; +function outstr = do_bold (str) + outstr = ['\textbf{' str '}']; endfunction -function outstr = handle_italic (str) - outstr = ["\\textit{", str, "}"]; +function outstr = do_italic (str) + outstr = ['\textit{' str '}']; endfunction -function outstr = handle_monospaced (str) - outstr = ["\\texttt{", str, "}"]; +function outstr = do_monospaced (str) + outstr = ['\texttt{' str '}']; endfunction -function outstr = handle_TM () - outstr = "\\texttrademark "; +function outstr = do_TM () + outstr = '\texttrademark '; endfunction -function outstr = handle_R () - outstr = "\\textregistered "; +function outstr = do_R () + outstr = '\textregistered '; endfunction function str = escape_latex (str) ## Escape "&", "%", "#", "_", "~", "^", "<", ">" - ## TODO: "\", "{", "}" - str = regexprep (str, '(?<!\\)(&)', "\\&"); - str = regexprep (str, '(?<!\\)(%)', "\\%"); - str = regexprep (str, '(?<!\\)(#)', "\\#"); - str = regexprep (str, '(?<!\\)(_)', "\\_"); + ## FIXME: What about: "\", "{", "}" + str = regexprep (str, '(?<!\\)(&|%|#|_)', '\\$1'); str = regexprep (str, '(?<!\\)(~)', "\\ensuremath{\\tilde{\;}}"); str = regexprep (str, '(?<!\\)(\^)', "\\^{}"); str = regexprep (str, '(?<!\\)(<)', "\\ensuremath{<}"); str = regexprep (str, '(?<!\\)(>)', "\\ensuremath{>}"); endfunction +
--- a/scripts/general/publish.m Mon Oct 31 12:22:43 2016 -0700 +++ b/scripts/general/publish.m Mon Oct 31 22:33:00 2016 -0700 @@ -18,108 +18,110 @@ ## <http://www.gnu.org/licenses/>. ## -*- 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{}) +## @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 reports from Octave script files in several output formats. +## Generate a report from the Octave script file @var{file} in one of 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}: +## 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*>. -## %% +## ## 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 () ## -## ## Octave comment style supported as well -## # -## # * Bulleted list item 1 -## # * Bulleted list item 2 -## # -## # # Numbered list item 1 -## # # Numbered list item 2 +## %% @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 ("example.m")}. +## To publish this script file, type @code{publish ("pub_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 separate 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. +## 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{filename}, @var{output_format})} is -## equivalent to the function call using a structure +## 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{filename}, @var{options}) +## publish (@var{file}, @var{options}) ## @end group ## @end example ## ## @noindent -## which is described below. The same holds for using option-value-pairs +## 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}) +## 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 considered: +## 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{xml}, or @samp{pdf}. +## @samp{pdf}, or @samp{xml}. ## -## 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. +## 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. ## ## @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. +## @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. +## @samp{createThumbnail} --- Not supported, only for @sc{matlab} +## compatibility. ## ## @item -## @samp{figureSnapMethod} --- Not supported, only for @sc{matlab} compatibility. +## @samp{figureSnapMethod} --- Not supported, only for @sc{matlab} +## compatibility. ## ## @item -## @samp{imageFormat} --- Desired format for images produced, while -## evaluating the code. The allowed image formats depend on the output -## format: +## @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} and @samp{xml} --- @samp{png} (default), any other -## image format supported by Octave +## @item @samp{html}, @samp{xml} --- @samp{png} (default), any image format +## supported by Octave ## -## @item @samp{latex} --- @samp{epsc2} (default), any other 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 @@ -129,15 +131,15 @@ ## @end itemize ## ## @item -## @samp{maxHeight} and @samp{maxWidth} --- Maximum height (width) of the +## @samp{maxWidth} and @samp{maxHeight} --- Maximum width (height) of the ## produced images in pixels. An empty value means no restriction. Both -## values have to be set, to work properly. +## 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 +## @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} @@ -148,18 +150,18 @@ ## @samp{true} (default) or @samp{false} ## ## @item -## @samp{catchError} --- Catch errors while code evaluation and continue +## @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 +## @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{maxOutputLines} --- Maximum number of output lines from code +## evaluation which are included in output. ## ## @samp{Inf} (default) or integer value > 0 ## @@ -170,18 +172,20 @@ ## @samp{true} (default) or @samp{false} ## @end itemize ## -## The returned @var{output_file} is a string with the path and file name +## 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) - narginchk (1, Inf); - nargoutchk (0, 1); + + if (nargin < 1) + print_usage (); + endif if (exist (file, "file") != 2) - error ("publish: FILE does not exist."); + error ("publish: FILE does not exist"); endif ## Check file to be in Octave's load path @@ -189,14 +193,15 @@ if (isempty (file_path)) file_path = pwd; endif - if (isempty (which ([file_name, file_ext]))) - error (["publish: ", file, " is not in the load path."]); + if (exist ([file_name, file_ext]) != 2) + error (["publish: " file " is not in the load path"]); endif ## Check file extension and for an Octave script + file_info = __which__ (file_name); - if ((! strcmp (file_ext, ".m")) || (! strcmp (file_info.type, "script"))) - error ("publish: Only Octave script files can be published."); + if (! strcmp (file_ext, ".m") || ! strcmp (file_info.type, "script")) + error ("publish: only script files can be published"); endif ## Check file to be parsable @@ -212,38 +217,35 @@ elseif (isstruct (varargin{1})) options = varargin{1}; else - error ("publish: Invalid second argument."); + 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)))) - for i = 1:2:numel(varargin) - options = setfield (options, varargin{i}, varargin{i + 1}); - endfor + 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 or inappropriate arguments."); + error ("publish: invalid 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 + options.format = validatestring (options.format, {"html", "doc", "latex", + "ppt", "xml", "pdf"}); + ## FIXME: Implement remaining formats if (any (strcmp (options.format, {"doc", "ppt", "xml"}))) - error ("publish: Output format currently not supported"); + error ('publish: Output format "%s" is not yet supported', + options.format); 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 = [file_path, filesep(), "html"]; + ## 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 @@ -257,8 +259,8 @@ ## Options for the figures if (! isfield (options, "createThumbnail")) options.createThumbnail = true; - elseif ((! isscalar (options.createThumbnail)) ... - || (! isbool (options.createThumbnail))) + elseif (! isscalar (options.createThumbnail) + || ! isreal (options.createThumbnail)) error ("publish: CREATETHUMBNAIL must be TRUE or FALSE"); endif @@ -267,8 +269,8 @@ else options.figureSnapMethod = validatestring (options.figureSnapMethod, ... {"entireGUIWindow", "print", "getframe", "entireFigureWindow"}); - ## TODO: implement - warning ("publish: option FIGURESNAPMETHOD currently not supported") + ## FIXME: implement other SnapMethods + warning ("publish: option FIGURESNAPMETHOD currently not supported"); endif if (! isfield (options, "imageFormat")) @@ -284,22 +286,21 @@ elseif (! ischar (options.imageFormat)) error ("publish: IMAGEFORMAT must be a string"); else - ## check valid imageFormat for chosen format + ## 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"}); + options.imageFormat = validatestring (options.imageFormat, + {"png", "jpg", "bmp", "tiff"}); case "pdf" - options.imageFormat = validatestring (options.imageFormat, ... - {"bmp", "jpg"}); + options.imageFormat = validatestring (options.imageFormat, + {"bmp", "jpg"}); endswitch endif if (! isfield (options, "maxHeight")) options.maxHeight = []; - elseif ((! isscalar (options.maxHeight)) ... - || (uint64 (options.maxHeight) == 0)) + elseif (! isscalar (options.maxHeight) || options.maxHeight < 1) error ("publish: MAXHEIGHT must be a positive integer"); else options.maxHeight = uint64 (options.maxHeight); @@ -307,8 +308,7 @@ if (! isfield (options, "maxWidth")) options.maxWidth = []; - elseif ((! isscalar (options.maxWidth)) ... - || (uint64 (options.maxWidth) == 0)) + elseif (! isscalar (options.maxWidth) || options.maxWidth < 1) error ("publish: MAXWIDTH must be a positive integer"); else options.maxWidth = uint64 (options.maxWidth); @@ -316,24 +316,24 @@ if (! isfield (options, "useNewFigure")) options.useNewFigure = true; - elseif (! isbool (options.useNewFigure)) + elseif (! isscalar (options.useNewFigure) || ! isreal (options.useNewFigure)) error ("publish: USENEWFIGURE must be TRUE or FALSE"); endif ## Options for the code - if (!isfield (options, "evalCode")) + if (! isfield (options, "evalCode")) options.evalCode = true; - elseif ((! isscalar (options.evalCode)) || (! isbool (options.evalCode))) + elseif (! isscalar (options.evalCode) || ! isreal (options.evalCode)) error ("publish: EVALCODE must be TRUE or FALSE"); endif - if (!isfield (options, "catchError")) + if (! isfield (options, "catchError")) options.catchError = true; - elseif ((! isscalar (options.catchError)) || (! isbool (options.catchError))) + elseif (! isscalar (options.catchError) || ! isreal (options.catchError)) error ("publish: CATCHERROR must be TRUE or FALSE"); endif - if (!isfield (options, "codeToEvaluate")) + if (! isfield (options, "codeToEvaluate")) options.codeToEvaluate = ""; elseif (! ischar (options.codeToEvaluate)) error ("publish: CODETOEVALUTE must be a string"); @@ -341,48 +341,48 @@ if (! isfield (options, "maxOutputLines")) options.maxOutputLines = Inf; - elseif (! isscalar (options.maxOutputLines)) + 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")) + if (! isfield (options, "showCode")) options.showCode = true; - elseif ((! isscalar (options.showCode)) || (! isbool (options.showCode))) + elseif (! isscalar (options.showCode) || ! isreal (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; + 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_struct = parse_m_source (doc_struct); + doc = parse_m_source (doc); ## Create output directory - [mkdir_stat, mkdir_msg] = mkdir (options.outputDir); - if (mkdir_stat != 1) - error ("publish: cannot create output directory. %s", mkdir_msg); + [status, msg] = mkdir (options.outputDir); + if (status != 1) + error ("publish: cannot create output directory: %s", msg); endif if (options.evalCode) - doc_struct = eval_code (doc_struct, options); + doc = eval_code (doc, options); endif - output_file = create_output (doc_struct, options); + output_file = create_output (doc, options); + endfunction - -function doc_struct = parse_m_source (doc_struct) +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_struct.m_source. + ## sections) given in doc.m_source. ## - ## The result is written to doc_struct.body, which then contains a cell + ## The result is written to doc.body, which then contains a cell ## vector of structs, either of ## ## a) {struct ("type", "code", ... @@ -392,12 +392,10 @@ ## "content", title_str)} ## ## Second parsing level is invoked for the paragraph contents, resulting - ## in more elements for doc_struct.body. - ## + ## in more elements for doc.body. - ## If there is nothing to parse - if (isempty (doc_struct.m_source)) - return; + if (isempty (doc.m_source)) + return; # Nothing to parse endif ## Parsing helper functions @@ -416,31 +414,29 @@ 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)); + 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_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 = {}; + 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_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)))); + 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_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) @@ -448,9 +444,9 @@ 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)) + 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_struct.m_source)]; + code_end_idx = [code_end_idx, length(doc.m_source)]; endif ## Remove overlaps idx = code_start_idx > code_end_idx; @@ -458,9 +454,9 @@ 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))))) + 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 @@ -471,70 +467,70 @@ ## 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)); + 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_struct.intro = parse_paragraph_content (content); + 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_struct + ## Add non-empty paragraphs and code to doc j = 1; i = (1 + title_offset); - while ((i <= length(par_start_idx)) || (j <= length(code_start_idx))) + 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 <= length(code_start_idx)) - && ((i > length(par_start_idx)) - || (par_start_idx(i) > code_start_idx(j)))) - doc_struct.body{end + 1}.type = "code"; + 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_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 = {}; + 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 <= length(par_start_idx)) + if (i <= numel (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 = 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_struct.body{end + 1}.type = type_str; - doc_struct.body{end}.content = title_str; + doc.body{end+1}.type = type_str; + doc.body{end}.content = title_str; endif - content = doc_struct.m_source(par_start_idx(i) + 1:par_end_idx(i)); + content = doc.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)]; + 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) +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 + ## returns a cell vector of structs, that can be appended to doc.body, + ## either of ## ## a) {struct ("type", "preformatted_code", ... ## "content", code_str)} @@ -561,7 +557,6 @@ ## * Inline "$" and block "$$" LaTeX math ## * Links ## * Trademark symbols - ## p_content = cell (); @@ -569,16 +564,16 @@ return; endif - ## Split into blocks seperated by empty lines + ## 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) + 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); + block = cellfun (@(c) cellstr (c(3:end)), block); p_content{end}.content = strjoin (block, "\n"); continue; endif @@ -586,7 +581,7 @@ ## 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); + block = cellfun (@(c) cellstr (c(2:end)), block); p_content{end}.content = strjoin (block, "\n"); continue; endif @@ -594,28 +589,29 @@ ## Bulleted list starts with "* " if (strncmp (block{1}, "* ", 2)) p_content{end+1}.type = "bulleted_list"; - p_content{end}.content = strjoin (block, "\n"); + tmpstr = strjoin (block, "\n"); ## Revove first "* " - p_content{end}.content = p_content{end}.content(3:end); + tmpstr = tmpstr(3:end); ## Split items - p_content{end}.content = strsplit (p_content{end}.content, "\n* "); + 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"; - p_content{end}.content = strjoin (block, "\n"); + tmpstr = strjoin (block, "\n"); ## Revove first "# " - p_content{end}.content = p_content{end}.content(3:end); + tmpstr = tmpstr(3:end); ## Split items - p_content{end}.content = strsplit (p_content{end}.content, "\n# "); + p_content{end}.content = strsplit (tmpstr, "\n# "); continue; endif ## Include <include>fname.m</include> - if (! isempty ([~,~,~,~,fname] = regexpi (strjoin (block, ""), ... - '^<include>(.*)<\/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})); @@ -625,8 +621,9 @@ endif ## Graphic <<myGraphic.png>> - if (! isempty ([~,~,~,~,fname] = regexpi (strjoin (block, ""), ... - '^<<(.*)>>$'))) + if (! isempty (fname = regexpi (strjoin (block, ""), + '^<<(.*)>>$', + "tokens"))) p_content{end+1}.type = "graphic"; p_content{end}.content = strtrim ((fname{1}){1}); continue; @@ -634,14 +631,14 @@ ## Parse remaining blocks line by line j = 1; - while (j <= length(block)) + while (j <= numel (block)) ## HTML markup if (strcmpi (block{j}, "<html>")) start_html = j + 1; - while ((j < length(block)) && ! strcmpi (block{j}, "</html>")) + while (j < numel (block) && ! strcmpi (block{j}, "</html>")) j++; endwhile - if ((j == length(block)) && ! strcmpi (block{j}, "</html>")) + if (j == numel (block) && ! strcmpi (block{j}, "</html>")) warning ("publish: no closing </html> found"); else j++; ## Skip closing tag @@ -653,13 +650,13 @@ ## LaTeX markup elseif (strcmpi (block{j}, "<latex>")) start_latex = j + 1; - while ((j < length(block)) && ! strcmpi (block{j}, "</latex>")) + while (j < numel (block) && ! strcmpi (block{j}, "</latex>")) j++; endwhile - if ((j == length(block)) && ! strcmpi (block{j}, "</latex>")) + if (j == numel (block) && ! strcmpi (block{j}, "</latex>")) warning ("publish: no closing </latex> found"); else - j++; ## Skrip closing tag + j++; ## Skip closing tag endif if (j > start_latex) p_content{end+1}.type = "latex"; @@ -672,9 +669,8 @@ ## * Inline "$" and block "$$" LaTeX math ## * Links ## * Trademark symbols - ## else - if ((j == 1) || isempty (p_content) ... + if (j == 1 || isempty (p_content) || ! strcmp (p_content{end}.type, "text")) p_content{end+1}.type = "text"; p_content{end}.content = block{j}; @@ -689,25 +685,21 @@ 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 + 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_struct, options) +function ofile = create_output (doc, options) ## CREATE_OUTPUT creates the desired output file - ## formatter = []; ofile_ext = ""; @@ -720,31 +712,32 @@ ofile_ext = ".tex"; endswitch - ## Use title, or if not given the m-file name - title_str = doc_struct.title; + ## Use title, or if not given, the m-file name + title_str = doc.title; if (isempty (title_str)) - [~,title_str] = fileparts (doc_struct.m_source_file_name); + [~, title_str] = fileparts (doc.m_source_file_name); endif - content = formatter ("header", title_str, ... - format_output (doc_struct.intro, formatter, options), ... - get_toc (doc_struct.body, formatter)); - content = [content, format_output(doc_struct.body, formatter, options)]; - content = [content, formatter("footer", strjoin (doc_struct.m_source, "\n"))]; + content = formatter ("header", 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_struct.m_source_file_name); + [~, ofile] = fileparts (doc.m_source_file_name); ofile_name = [ofile, ofile_ext]; - ofile = [options.outputDir, filesep(), ofile_name]; + 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"); + 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 @@ -752,33 +745,29 @@ 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. -function toc_cstr = get_toc (cstr, formatter) - ## 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) + for i = 1:numel (cstr) if (strcmp (cstr{i}.type, "section")) - toc_cstr{end + 1} = format_text (cstr{i}.content, formatter); + 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_struct.intro or - ## doc_struct.body) in cstr and produces a single result string - ## with the source code in the desired output format. + ## 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 knowlegde how to enforce the target 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:length(cstr) + for i = 1:numel (cstr) switch (cstr{i}.type) case "code" if (options.showCode) @@ -789,23 +778,22 @@ endif case {"text", "section"} str = [str, formatter(cstr{i}.type, ... - format_text (cstr{i}.content, formatter))]; + format_text (cstr{i}.content, formatter))]; case {"bulleted_list", "numbered_list"} items = cellfun (@(str) format_text(str, formatter), ... - cstr{i}.content, "UniformOutput", false); + 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) - ## ## Regular expressions for the formats: ## @@ -813,7 +801,6 @@ ## * Links "<octave:Function TEXT>" ## * Links "<http://www.someurl.com TEXT>" ## * inline "$" and block "$$" LaTeX math - ## regexes = {'<\S{3,}[^\s<>]*>', ... '<octave:[^\s<>]* *[^<>$]*>', ... '<\S{3,}[^\s<>]* *[^<>$]*>', ... @@ -821,28 +808,27 @@ '\*[^*]*\*', ... # Bold '_[^_]*_', ... # Italic '\|[^|]*\|'}; # Monospaced - - ## Helper 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 + ## 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 occurances with placeholders + ## Substitute all occurrences with placeholders placeholder_cstr = {}; plh = 0; - for i = 1:length(regexes) - [~,~,~,cstr] = regexp (str, regexes{i}); - for j = 1:length(cstr) - plh = plh + 1; - str = regexprep (str, regexes{i}, ... - ["PUBLISHPLACEHOLDER", num2str(plh)], "once"); + 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>" + ## 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>" + ## Links "<octave:Function TEXT>" idx = strfind (cstr{j}, " "); url = cstr{j}; url = ["https://www.gnu.org/software/octave/doc/interpreter/", ... @@ -851,7 +837,7 @@ txt = format_text (txt(idx+1:end-1), formatter); cstr{j} = formatter ("link", url, txt); case 3 - # Links "<http://www.someurl.com TEXT>" + ## Links "<http://www.someurl.com TEXT>" idx = strfind (cstr{j}, " "); url = cstr{j}; url = url(2:idx-1); @@ -859,51 +845,50 @@ txt = format_text (txt(idx+1:end-1), formatter); cstr{j} = formatter ("link", url, txt); case 4 - # inline "$" and block "$$" LaTeX math --> do nothing + ## inline "$" and block "$$" LaTeX math --> do nothing case 5 - # Bold + ## Bold txt = cstr{j}; cstr{j} = formatter ("bold", format_text (txt(2:end-1), formatter)); case 6 - # Italic + ## Italic txt = cstr{j}; cstr{j} = formatter ("italic", format_text (txt(2:end-1), formatter)); case 7 - # Monospaced + ## Monospaced txt = cstr{j}; cstr{j} = formatter ("monospaced", format_text (txt(2:end-1), ... - formatter)); + formatter)); endswitch endfor placeholder_cstr = [placeholder_cstr, cstr]; endfor ## Replace special symbols - str = strrep (str, "(TM)", formatter("TM")); - str = strrep (str, "(R)", formatter("R")); + str = strrep (str, "(TM)", formatter ("TM")); + str = strrep (str, "(R)", formatter ("R")); ## Restore placeholders for i = plh:-1:1 - str = strrep (str, ["PUBLISHPLACEHOLDER", num2str(i)], ... - placeholder_cstr{i}); + str = strrep (str, ["PUBLISHPLACEHOLDER" sprintf("%d", i)], + placeholder_cstr{i}); endfor + endfunction - -function doc_struct = eval_code (doc_struct, options) - ## EVAL_CODE Thrid level parsing +function doc = eval_code (doc, options) + ## EVAL_CODE Third 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"); + ## 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_struct.m_source_file_name); + [~, fig_name] = fileparts (doc.m_source_file_name); fig_num = 1; fig_list = struct (); @@ -918,44 +903,44 @@ 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"); + 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_struct.body{i}.output = eval_code_helper (tmp_context, code_str); + doc.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]); + doc.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); + 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:length(fig_ids_new) + for j = 1:numel (fig_ids_new) drawnow (); if (isempty (get (fig_ids_new(j), "children"))) continue; endif - fname = [fig_name, "-", num2str(fig_num), "."]; + fname = [fig_name, "-", sprintf("%d", fig_num)]; if (strncmp (options.imageFormat, "eps", 3)) - fname = [fname, "eps"]; + fname = [fname ".eps"]; else - fname = [fname, options.imageFormat]; + fname = [fname "." options.imageFormat]; endif print_opts = {fig_ids_new(j), ... - [options.outputDir, filesep(), fname], ... - ["-d", options.imageFormat], "-color"}; + fullfile(options.outputDir, fname), ... + ["-d" options.imageFormat], "-color"}; if (! isempty (options.maxWidth) && ! isempty (options.maxHeight)) - print_opts{end + 1} = ["-S", num2str(options.maxWidth), ... - num2str(options.maxHeight)]; + print_opts{end+1} = sprintf("-S%d,%d", options.maxWidth, + options.maxHeight); elseif (! isempty (options.maxWidth) || ! isempty (options.maxWidth)) warning (["publish: specify both options.maxWidth ", ... - "and options.maxWidth"]); + "and options.maxWidth"]); endif print (print_opts{:}); fig_num++; @@ -963,26 +948,25 @@ 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]; + fig_elem = [getfield(fig_list, sprintf ("%d", i)), fig_elem]; endif - fig_list = setfield (fig_list, num2str (i), fig_elem); + 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) + && ! 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); + if (options.maxOutputLines < length (doc.body{i}.output)) + doc.body{i}.output = doc.body{i}.output(1:options.maxOutputLines); endif - doc_struct.body{i}.output = strjoin (doc_struct.body{i}.output, "\n"); + doc.body{i}.output = strjoin (doc.body{i}.output, "\n"); endif endfor - ## Close any by publish opened figures + ## Close any figures opened by publish function delete (setdiff (findall (0, "type", "figure"), fig_ids)); ## Remove temporary context @@ -991,23 +975,23 @@ ## Insert figures to document fig_code_blocks = fieldnames (fig_list); body_offset = 0; - for i = 1:length(fig_code_blocks) + for i = 1:numel (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); + ## 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 + ## temporary context and returns a cellstring with the eval output. - ## TODO: potential conflicting variables sourrounded by "___". Maybe there - ## is a better solution. + ## FIXME: Potential conflicting variables surrounded by "___". + ## Maybe there is a better solution? if (isempty (___code___)) return; endif @@ -1018,17 +1002,18 @@ ___cstr___ = strsplit (evalc (___code___), "\n"); - clear ___code___ + clear ___code___; save (___context___); endfunction +## FIXME: Missing any functional BIST tests -## Bad function calls - +## Test input validation %!error publish () %!error publish (1) %!error publish ("non_existing_file.m") %!error<Only Octave script files can be published> publish ("publish.m") %!error publish ("test_script.m", "format", "html", "showCode") %!error [str1, str2] = publish ("test_script.m") +