From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- macros/latex/contrib/sagetex/py-and-sty.dtx | 1575 +++++++++++++++++++++++++++ 1 file changed, 1575 insertions(+) create mode 100644 macros/latex/contrib/sagetex/py-and-sty.dtx (limited to 'macros/latex/contrib/sagetex/py-and-sty.dtx') diff --git a/macros/latex/contrib/sagetex/py-and-sty.dtx b/macros/latex/contrib/sagetex/py-and-sty.dtx new file mode 100644 index 0000000000..a27dbc5bd8 --- /dev/null +++ b/macros/latex/contrib/sagetex/py-and-sty.dtx @@ -0,0 +1,1575 @@ +% \section{Implementation} +% \label{sec:implementation} +% +% There are two pieces to this package: a \LTX style file, and a +% Python module. They are mutually interdependent, so it makes sense to +% document them both here. +% +% \subsection{The style file} +% \label{sec:sty-file} +% +% \iffalse +% tell docstrip to put code into the .sty file +%<*latex> +% \fi +% +% All macros and counters intended for use internal to this package +% begin with ``|ST@|''. +% +% \subsubsection{Initialization} +% +% Let's begin by loading some packages. The key bits of |sageblock| and +% friends are stol---um, adapted from the |verbatim| package manual. So +% grab the |verbatim| package. We also need the |fancyvrb| package for +% the |sageexample| environment +% \begin{macrocode} +\RequirePackage{verbatim} +\RequirePackage{fancyvrb} +% \end{macrocode} +% and |listings| for the |sagecommandline| environment. +% \begin{macrocode} +\RequirePackage{listings} +\RequirePackage{color} +\lstdefinelanguage{Sage}[]{Python} + {morekeywords={False,sage,True},sensitive=true} +\lstdefinelanguage{SageOutput}[]{} + {morekeywords={False,True},sensitive=true} +\lstdefinestyle{DefaultSageInputOutput}{ + nolol, + identifierstyle=, + name=sagecommandline, + xleftmargin=5pt, + numbersep=5pt, + aboveskip=0pt, + belowskip=0pt, + breaklines=true, + numberstyle=\footnotesize, + numbers=right +} +\lstdefinestyle{DefaultSageInput}{ + language=Sage, + style=DefaultSageInputOutput, + basicstyle={\ttfamily\bfseries}, + commentstyle={\ttfamily\color{dgreencolor}}, + keywordstyle={\ttfamily\color{dbluecolor}\bfseries}, + stringstyle={\ttfamily\color{dgraycolor}\bfseries}, +} +\lstdefinestyle{DefaultSageOutput}{ + language=SageOutput, + style=DefaultSageInputOutput, + basicstyle={\ttfamily}, + commentstyle={\ttfamily\color{dgreencolor}}, + keywordstyle={\ttfamily\color{dbluecolor}}, + stringstyle={\ttfamily\color{dgraycolor}}, +} +\lstdefinestyle{SageInput}{ + style=DefaultSageInput, +} +\lstdefinestyle{SageOutput}{ + style=DefaultSageOutput, +} +\definecolor{dbluecolor}{rgb}{0.01,0.02,0.7} +\definecolor{dgreencolor}{rgb}{0.2,0.4,0.0} +\definecolor{dgraycolor}{rgb}{0.30,0.3,0.30} +% \end{macrocode} +% Unsurprisingly, the |\sageplot| command works poorly without graphics +% support. +% \begin{macrocode} +\RequirePackage{graphicx} +% \end{macrocode} +% The |makecmds| package gives us a |\provideenvironment| which we need, +% and we use |ifpdf| and |ifthen| in |\sageplot| so we know what kind of +% files to look for. Since |ifpdf| doesn't detect running under XeTeX +% (which defaults to producing PDFs), we need |ifxetex|. Hopefully the +% |ifpdf| package will get support for this and we can drop |ifxetex|. +% We also work around ancient \TeX{} distributions that don't have +% |ifxetex| and assume that they don't have XeTeX. +% \begin{macrocode} +\RequirePackage{makecmds} +\RequirePackage{ifpdf} +\RequirePackage{ifthen} +\IfFileExists{ifxetex.sty}{ + \RequirePackage{ifxetex} +}{ + \newboolean{xetex} + \setboolean{xetex}{false}} +% \end{macrocode} +% +% Next set up the counters, default indent, and flags. +% \begin{macrocode} +\newcounter{ST@inline} +\newcounter{ST@plot} +\newcounter{ST@cmdline} +\setcounter{ST@inline}{0} +\setcounter{ST@plot}{0} +\setcounter{ST@cmdline}{0} +\newlength{\sagetexindent} +\setlength{\sagetexindent}{5ex} +\newif\ifST@paused +\ST@pausedfalse +% \end{macrocode} +% Set up the file stuff, which will get run at the beginning of the +% document, after we know what's happening with the |final| option. +% First, we open the |.sage| file: +% \begin{macrocode} +\AtBeginDocument{\@ifundefined{ST@final}{% +\newwrite\ST@sf% +\immediate\openout\ST@sf=\jobname.sagetex.sage% +% \end{macrocode} +% \begin{macro}{\ST@wsf} +% We will write a lot of stuff to that file, so make a convenient +% abbreviation, then use it to put the initial commands into the +% |.sage| file. The hash mark below gets doubled when written to the +% file, for some obscure reason related to parameter expansion. It's +% valid Python, though, so I haven't bothered figuring out how to get +% a single hash. We are assuming that the extension is |.tex|; see the +% |initplot| documentation on page~\pageref{initplot} for discussion +% of file extensions. (There is now the \texttt{currfile} package +% (\url{http://www.ctan.org/pkg/currfile/}) which can figure out file +% extensions, apparently.) The ``|(\jobname.sagetex.sage)|'' business +% is there because the comment below will get pulled into the +% autogenerated |.py| file (second order autogeneration!) and I'd like +% to reduce possible confusion if someone is looking around in those +% files. Finally, we check for version mismatch and bail if the .py +% and .sty versions don't match and the user hasn't disabled checking. +% Note that we use |^^J| and not |^^J%| when we need indented lines. +% Also, |sagetex.py| now includes a |version| variable which +% eliminates all the irritating string munging below, and later we can +% remove this stuff and just use |sagetex.version|. +% \changes{v2.2.4}{2010/02/15}{Add version mismatch checking.} +% \changes{v2.3.3}{2012/01/13}{Improve version mismatch checking, +% include Mercurial revision in version string.} +% \begin{macrocode} +\newcommand{\ST@wsf}[1]{\immediate\write\ST@sf{#1}}% +\ST@wsf{% +# -*- encoding: utf-8 -*-^^J% +# This file (\jobname.sagetex.sage) was *autogenerated* from \jobname.tex with +sagetex.sty version \ST@ver.^^J% +import sagetex^^J% +_st_ = sagetex.SageTeXProcessor('\jobname', version='\ST@ver', version_check=\ST@versioncheck)}}% +% \end{macrocode} +% On the other hand, if the |ST@final| flag is set, don't bother with +% any of the file stuff, and make |\ST@wsf| a no-op. +% \begin{macrocode} +{\newcommand{\ST@wsf}[1]{\relax}}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ST@dodfsetup} +% The |sageexample| environment writes stuff out to a different file +% formatted so that one can run doctests on it. We define a macro that +% only sets this up if necessary. +% \begin{macrocode} +\newcommand{\ST@dodfsetup}{% +\@ifundefined{ST@diddfsetup}{% +\newwrite\ST@df% +\immediate\openout\ST@df=\jobname_doctest.sage% +\immediate\write\ST@df{r"""^^J% +This file was *autogenerated* from \jobname.tex with sagetex.sty^^J% +version \ST@ver. It contains the contents of all the^^J% +sageexample environments from \jobname.tex. You should be able to^^J% +doctest this file with "sage -t \jobname_doctest.sage".^^J% +^^J% +It is always safe to delete this file; it is not used in typesetting your^^J% +document.^^J}% +\AtEndDocument{\immediate\write\ST@df{"""}}% +\gdef\ST@diddfsetup{x}}% +{\relax}} +% \end{macrocode} +% \changes{v2.2.5}{2010/03/25}{Write sageexample environment contents to +% a separate file, formatted for doctesting} +% \end{macro} +% +% \begin{macro}{\ST@wdf} +% This is the compansion to |\ST@wsf|; it writes to the doctest file, +% assuming that is has been set up. We ignore the |final| option here +% since nothing in this file is relevant to typesetting the document. +% \begin{macrocode} +\newcommand{\ST@wdf}[1]{\immediate\write\ST@df{#1}} +% \end{macrocode} +% \end{macro} +% +% Now we declare our options, which mostly just set flags that we check +% at the beginning of the document, and when running the |.sage| file. +% \changes{v2.0}{2008/04/04}{Add \texttt{epstopdf} option} +% \changes{v2.0}{2008/12/16}{Add \texttt{final} option} +% +% The |final| option controls whether or not we write the |.sage| file; +% the |imagemagick| and |epstopdf| options both want to write something +% to that same file. So we put off all the actual file stuff until the +% beginning of the document---by that time, we'll have processed the +% |final| option (or not) and can check the |\ST@final| flag to see what +% to do. (We must do this because we can't specify code that runs if an +% option \emph{isn't} defined.) +% +% For |final|, we set a flag for other guys to check, and if there's no +% |.sout| file, we warn the user that something fishy is going on. +% \begin{macrocode} +\DeclareOption{final}{% + \newcommand{\ST@final}{x}% + \IfFileExists{\jobname.sagetex.sout}{}{\AtEndDocument{\PackageWarningNoLine{sagetex}% + {`final' option provided, but \jobname.sagetex.sout^^Jdoesn't exist! No Sage + input will appear in your document. Remove the `final'^^Joption and + rerun LaTeX on your document}}}} +% \end{macrocode} +% For |imagemagick|, we set two flags: one for \LTX and one for Sage. +% It's important that we set |ST@useimagmagick| \emph{before} the +% beginning of the document, so that the graphics commands can check +% that. We do wait until the beginning of the document to do file +% writing stuff. +% \begin{macrocode} +\DeclareOption{imagemagick}{% + \newcommand{\ST@useimagemagick}{x}% + \AtBeginDocument{% + \@ifundefined{ST@final}{% + \ST@wsf{_st_.useimagemagick = True}}{}}} +% \end{macrocode} +% For |epstopdf|, we just set a flag for Sage. +% \begin{macrocode} +\DeclareOption{epstopdf}{% +\AtBeginDocument{% +\@ifundefined{ST@final}{% + \ST@wsf{_st_.useepstopdf = True}}{}}} +% \end{macrocode} +% By default, we check to see if the .py and .sty file versions match. +% But we let the user disable this. +% \begin{macrocode} +\newcommand{\ST@versioncheck}{True} +\DeclareOption{noversioncheck}{% + \renewcommand{\ST@versioncheck}{False}} +\ProcessOptions\relax +% \end{macrocode} +% The |\relax| is a little incantation suggested by the ``\LaTeXe{} for +% class and package writers'' manual, section 4.7. +% +% Pull in the |.sout| file if it exists, or do nothing if it doesn't. I +% suppose we could do this inside an |AtBeginDocument| but I don't see +% any particular reason to do that. It will work whenever we load it. If +% the |.sout| file isn't found, print the usual \TeX-style message. This +% allows programs +% (\href{http://www.phys.psu.edu/~collins/software/latexmk-jcc/}{\texttt{Latexmk}}, for example) +% that read the |.log| file or terminal output to detect +% the need for another typesetting run to do so. If the ``\texttt{No +% file foo.sout}'' line doesn't work for some software package, please +% let me know and I can change it to use |PackageInfo| or whatever. +% \begin{macrocode} +\InputIfFileExists{\jobname.sagetex.sout}{} +{\typeout{No file \jobname.sagetex.sout.}} +% \end{macrocode} +% \changes{v2.1.1}{2009/05/14}{Add typeout if .sout file not found} +% +% The user might load the |hyperref| package after this one (indeed, the +% |hyperref| documentation insists that it be loaded last) or not at +% all---so when we hit the beginning of the document, provide a dummy +% |NoHyper| environment if one hasn't been defined by the |hyperref| +% package. We need this for the |\sage| macro below. +% \begin{macrocode} +\AtBeginDocument{\provideenvironment{NoHyper}{}{}} +% \end{macrocode} +% +% \subsubsection{The \texttt{\protect\bslash sage} and +% \texttt{\protect\bslash sagestr} macros} +% \label{sec:sagemacro} +% +% \begin{macro}{\ST@sage} +% \changes{v2.1.1}{2009/05/14}{Add ST@sage, sagestr, and refactor.} +% This macro combines |\ref|, |\label|, and Sage all at once. First, we +% use Sage to get a \LTX representation of whatever you give this +% function. The Sage script writes a |\newlabel| line into the |.sout| +% file, and we read the output using the |\ref| command. Usually, |\ref| +% pulls in a section or theorem number, but it will pull in arbitrary +% text just as well. +% +% The first thing it does it write its argument into the |.sage| file, +% along with a counter so we can produce a unique label. We wrap a +% try/except around the function call so that we can provide a more +% helpful error message in case something goes wrong. (In particular, we +% can tell the user which line of the |.tex| file contains the offending +% code.) Note the difference between |^^J| and |^^J%|: the newline +% immediately after the former puts a space into the output, and the +% percent sign in the latter supresses this. +% \begin{macrocode} +\newcommand{\ST@sage}[1]{\ST@wsf{% +try:^^J + _st_.current_tex_line = \the\inputlineno^^J + _st_.inline(\theST@inline, #1)^^J% +except:^^J + _st_.goboom(\the\inputlineno)}% +% \end{macrocode} +% The |inline| function of the Python module is documented on page +% \pageref{inlinefn}. Back in \LTX-land: if paused, say so. +% \begin{macrocode} +\ifST@paused + \mbox{(Sage\TeX{} is paused)}% +% \end{macrocode} +% Otherwise\ldots our use of |\newlabel| and |\ref| seems awfully clever +% until you load the |hyperref| package, which gleefully tries to +% hyperlink the hell out of everything. This is great until it hits one +% of our special |\newlabel|s and gets deeply confused. Fortunately the +% |hyperref| folks are willing to accomodate people like us, and give us +% a |NoHyper| environment. +% \begin{macrocode} +\else + \begin{NoHyper}\ref{@sageinline\theST@inline}\end{NoHyper}% +% \end{macrocode} +% Now check if the label has already been defined. (The internal +% implementation of labels in \LTX involves defining a macro called +% ``|r@@labelname|''.) If it hasn't, we set a flag so that we can tell +% the user to run Sage on the |.sage| file at the end of the run. +% \begin{macrocode} + \@ifundefined{r@@sageinline\theST@inline}{\gdef\ST@rerun{x}}{}% +\fi +% \end{macrocode} +% In any case, the last thing to do is step the counter. +% \begin{macrocode} +\stepcounter{ST@inline}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\sage} +% This is the user-visible macro; it runs Sage's |latex()| on its +% argument. +% \begin{macrocode} +\newcommand{\sage}[1]{\ST@sage{latex(#1)}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\sagestr} +% Like above, but doesn't run |latex()| on its argument. +% \begin{macrocode} +\newcommand{\sagestr}[1]{\ST@sage{#1}} +% \end{macrocode} +% +% \end{macro} +% +% \begin{macro}{\percent} +% A macro that inserts a percent sign. This is more-or-less stolen from the +% \textsf{Docstrip} manual; there they change the catcode inside a group +% and use |gdef|, but here we try to be more \LaTeX y and use +% |\newcommand|. +% \begin{macrocode} +\catcode`\%=12 +\newcommand{\percent}{%} +\catcode`\%=14 +% \end{macrocode} +% \end{macro} +% +% \subsubsection{The \texttt{\protect\bslash sageplot} macro and friends} +% \label{sec:sageplotmacro} +% +% Plotting is rather more complicated, and requires several helper +% macros that accompany |\sageplot|. +% +% \begin{macro}{\ST@plotdir} +% A little abbreviation for the plot directory. We don't use +% |\graphicspath| because it's +% \href{http://www.tex.ac.uk/cgi-bin/texfaq2html?label=graphicspath}{ +% apparently slow}---also, since we know right where our plots are +% going, no need to have \LTX looking for them. +% \begin{macrocode} +\newcommand{\ST@plotdir}{sage-plots-for-\jobname.tex} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ST@missingfilebox} +% The code that makes the ``file not found'' box. This shows up in a +% couple places below, so let's just define it once. +% \begin{macrocode} +\newcommand{\ST@missingfilebox}{\framebox[2cm]{\rule[-1cm]{0cm}{2cm}\textbf{??}}} +% \end{macrocode} +% \end{macro} +% \begin{macro}{\sageplot} +% \changes{v1.3}{2008/03/08}{Iron out warnings, cool \TikZ flowchart} +% This function is similar to |\sage|. The neat thing that we take +% advantage of is that commas aren't special for arguments to \LTX +% commands, so it's easy to capture a bunch of keyword arguments that +% get passed right into a Python function. +% +% This macro has two optional arguments, which can't be defined using +% \LTX's |\newcommand|; we use Scott Pakin's brilliant +% \href{http://tug.ctan.org/tex-archive/support/newcommand/}{|newcommand|} +% package to create this macro; the options I fed to his script were +% similar to this: +%\begin{center} +% |MACRO sageplot OPT[#1={width}] OPT[#2={notprovided}] #3| +%\end{center} +% Observe that we are using a Python script to write \LTX code which +% writes Python code which writes \LTX code. Crazy! +% +% Here's the wrapper command which does whatever magic we need to get +% two optional arguments. +% \begin{macrocode} +\newcommand{\sageplot}[1][]{% + \@ifnextchar[{\ST@sageplot[#1]}{\ST@sageplot[#1][notprovided]}} +% \end{macrocode} +% \changes{v2.3.2}{2011/06/17}{Remove ``.75 textwidth'' default option} +% The first optional argument |#1| will get shoved right into the +% optional argument for |\includegraphics|, so the user has easy control +% over the \LTX aspects of the plotting. (Perhaps a future version +% of \ST will allow the user to specify in the package options a set of +% default options to be used throughout.) The second optional argument +% |#2| is the file format and allows us to tell what files to look for. +% It defaults to ``notprovided'', which tells the Python module to +% create EPS and PDF files. Everything in |#3| gets put into the Python +% function call, so the user can put in keyword arguments there which +% get interpreted correctly by Python. +% +% \begin{macro}{\ST@sageplot} +% \changes{v2.0}{2008/12/16}{Change to use only keyword arguments: see issue +% 2 on bitbucket tracker} +% Let's see the real code here. We write a couple lines to the |.sage| +% file, including a counter, input line number, and all of the mandatory +% argument; all this is wrapped in another try/except. +% \begin{macrocode} +\def\ST@sageplot[#1][#2]#3{\ST@wsf{try:^^J + _st_.current_tex_line = \the\inputlineno^^J + _st_.plot(\theST@plot, format='#2', _p_=#3)^^Jexcept:^^J + _st_.goboom(\the\inputlineno)}% +% \end{macrocode} +% The Python |plot| function is documented on page~\pageref{plotfn}. +% +% Now we include the appropriate graphics file. Because the user might +% be producing DVI or PDF files, and have supplied a file format or not, +% and so on, the logic we follow is a bit complicated. +% \autoref{f:sageplottree} shows what we do; for completeness---and +% because I think drawing trees with \TikZ is really cool---we show what +% |\ST@inclgrfx| does in \autoref{f:stig}. This entire complicated +% business is intended to avoid doing an |\includegraphics| command on a +% file that doesn't exist, and to issue warnings appropriate to the +% situation. +% +% \begin{figure} +% \centering +% \begin{tikzpicture} +% \tikzstyle{level 1}=[sibling distance=6cm] +% \tikzstyle{level 2}=[sibling distance=3cm] +% \node [box] {DVI or PDF?} +% child {node [box] {Format provided?} +% child {node [box] {STig EPS} +% edge from parent node[left] {no}} +% child {node [box] {IM option set?} +% child {node [box, text width=3cm] {Warn that DVI + PNG = bad} +% edge from parent node[left] {no}} +% child {node [box] {STig EPS} +% edge from parent node[right] {yes}} +% edge from parent node[right] {yes}} +% edge from parent node[left] {DVI}} +% child {node [box] {Format provided?} +% child {node [box] {STig PDF} +% edge from parent node[left] {no}} +% child {node [box] {STig \texttt{\#2}} +% edge from parent node[right] {yes}} +% edge from parent node[right] {PDF}}; +% \end{tikzpicture} +% \caption{The logic tree that \texttt{\bslash sageplot} uses to +% decide whether to run \texttt{\bslash includegraphics} or to yell at +% the user. ``Format'' is the \texttt{\#2} argument to \texttt{\bslash +% sageplot}, ``STig ext'' +% means a call to \texttt{\bslash ST@inclgrfx} with ``ext'' as the +% second argument, and ``IM'' is Imagemagick.} +% \label{f:sageplottree} +% \end{figure} +% +% If we are creating a PDF, we check to see if the user asked for a +% different format, and use that if necessary: +% \begin{macrocode} +\ifthenelse{\boolean{pdf} \or \boolean{xetex}}{ + \ifthenelse{\equal{#2}{notprovided}}% + {\ST@inclgrfx{#1}{pdf}}% + {\ST@inclgrfx{#1}{#2}}} +% \end{macrocode} +% Otherwise, we are creating a DVI file, which only supports EPS. If the +% user provided a format anyway, don't include the file (since it won't +% work) and warn the user about this. (Unless the file doesn't exist, in +% which case we do the same thing that |\ST@inclgrfx| does.) +% \begin{macrocode} +{ \ifthenelse{\equal{#2}{notprovided}}% + {\ST@inclgrfx{#1}{eps}}% +% \end{macrocode} +% If a format is provided, we check to see if we're using the +% imagemagick option. If not, we're going to issue some sort of warning, +% depending on whether the file exists yet or not. +% \begin{macrocode} + {\@ifundefined{ST@useimagemagick}% + {\IfFileExists{\ST@plotdir/plot-\theST@plot.#2}% + {\ST@missingfilebox% + \PackageWarning{sagetex}{Graphics file + \ST@plotdir/plot-\theST@plot.#2\space on page \thepage\space + cannot be used with DVI output. Use pdflatex or create an EPS + file. Plot command is}}% + {\ST@missingfilebox% + \PackageWarning{sagetex}{Graphics file + \ST@plotdir/plot-\theST@plot.#2\space on page \thepage\space + does not exist. Plot command is}% + \gdef\ST@rerun{x}}}% +% \end{macrocode} +% Otherwise, we are using Imagemagick, so try to include an EPS file +% anyway. +% \begin{macrocode} + {\ST@inclgrfx{#1}{eps}}}} +% \end{macrocode} +% Step the counter and we're done with the usual work. +% \begin{macrocode} +\stepcounter{ST@plot}} +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\ST@inclgrfx} +% This command includes the requested graphics file (|#2| is the +% extension) with the requested options (|#1|) if the file exists. Note +% that it just needs to know the extension, since we use a counter for +% the filename. If we are paused, it just puts in a little box saying +% so. +% \begin{macrocode} +\newcommand{\ST@inclgrfx}[2]{\ifST@paused + \fbox{\rule[-1cm]{0cm}{2cm}Sage\TeX{} is paused; no graphic} +\else + \IfFileExists{\ST@plotdir/plot-\theST@plot.#2}% + {\includegraphics[#1]{\ST@plotdir/plot-\theST@plot.#2}}% +% \end{macrocode} +% If the file doesn't exist, we try one more thing before giving up: the +% Python module will automatically fall back to saving as a PNG file if +% saving as an EPS or PDF file fails. So if making a PDF, we look for a +% PNG file. +% +% If the file isn't there, we insert a little box to indicate it wasn't +% found, issue a warning that we didn't find a graphics file, then set a +% flag that, at the end of the run, tells the user to run Sage again. +% \begin{macrocode} + {\IfFileExists{\ST@plotdir/plot-\theST@plot.png}% + {\ifpdf + \ST@inclgrfx{#1}{png} + \else + \PackageWarning{sagetex}{Graphics file + \ST@plotdir/plot-\theST@plot.png on page \thepage\space not + supported; try using pdflatex. Plot command is}% + \fi}% + {\ST@missingfilebox% + \PackageWarning{sagetex}{Graphics file + \ST@plotdir/plot-\theST@plot.#2\space on page \thepage\space does not + exist. Plot command is}% + \gdef\ST@rerun{x}}} +\fi} +% \end{macrocode} +% \autoref{f:stig} makes this a bit clearer. +% \begin{figure} +% \centering +% \begin{tikzpicture} +% \tikzstyle{level 1}=[sibling distance=4cm] +% \node [box] {Paused?} +% child {node [box] {Insert ``we're paused'' box} +% edge from parent node[left] {yes}} +% child {node [box] {Does EXT file exist?} +% child {node [box] {Does a PNG file exist?} +% child {node [box] {Making a PDF?} +% child {node [box] {\texttt{includegraphics} PNG} +% edge from parent node[left] {yes}} +% child {node [box, text width=2cm] {Warning: DVI, PNG incompatible} +% edge from parent node[left] {no}} +% edge from parent node[left] {yes}} +% child {node [box, text width = 2.125cm] {Warn user to rerun Sage} +% edge from parent node[left] {no}} +% edge from parent node[left] {no}} +% child {node [box] {Use \texttt{includegraphics}} +% edge from parent node[right] {yes}} +% edge from parent node[right] {no}}; +% \end{tikzpicture} +% \caption{The logic used by the \texttt{\bslash ST@inclgrfx} +% command.} +% \label{f:stig} +% \end{figure} +% \end{macro} +% +% \subsubsection{Verbatim-like environments} +% \label{sec:verbatim-envs} +% +% \begin{macro}{\ST@beginsfbl} +% This is ``begin |.sage| file block'', an internal-use abbreviation +% that sets things up when we start writing a chunk of Sage code to +% the |.sage| file. It begins with some \TeX{} magic that fixes +% spacing, then puts the start of a try/except block in the |.sage| +% file---this not only allows the user to indent code without +% Sage/Python complaining about indentation, but lets us tell the user +% where things went wrong. The |blockbegin| and |blockend| functions +% are documented on page~\pageref{blocksbeginend}. The last bit is some +% magic from the |verbatim| package manual that makes \LTX respect +% line breaks. +% \begin{macrocode} +\newcommand{\ST@beginsfbl}{% + \@bsphack\ST@wsf{% +_st_.current_tex_line = \the\inputlineno^^J% +_st_.blockbegin()^^Jtry:}% + \let\do\@makeother\dospecials\catcode`\^^M\active} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ST@endsfbl} +% The companion to |\ST@beginsfbl|. +% \begin{macrocode} +\newcommand{\ST@endsfbl}{% +\ST@wsf{except:^^J + _st_.goboom(\the\inputlineno)^^J_st_.blockend()}} +% \end{macrocode} +% \end{macro} +% +% Now let's define the ``verbatim-like'' environments. There are four +% possibilities, corresponding to the two independent choices of +% typesetting the code or not, and writing to the |.sage| file or not. +% +% \begin{environment}{sageblock} +% This environment does both: it typesets your code and puts it into the +% |.sage| file for execution by Sage. +% \begin{macrocode} +\newenvironment{sageblock}{\ST@beginsfbl% +% \end{macrocode} +% The space between |\ST@wsf{| and |\the| is crucial! It, along with the +% ``|try:|'', is what allows the user to indent code if they like. This +% line sends stuff to the |.sage| file. +% \begin{macrocode} +\def\verbatim@processline{\ST@wsf{ \the\verbatim@line}% +% \end{macrocode} +% Next, we typeset your code and start the verbatim environment. +% \begin{macrocode} +\hspace{\sagetexindent}\the\verbatim@line\par}% +\verbatim}% +% \end{macrocode} +% At the end of the environment, we put a chunk into the |.sage| file +% and stop the verbatim environment. +% \begin{macrocode} +{\ST@endsfbl\endverbatim} +% \end{macrocode} +% \end{environment} +% +% \begin{environment}{sagesilent} +% This is from the |verbatim| package manual. It's just like the above, +% except we don't typeset anything. +% \begin{macrocode} +\newenvironment{sagesilent}{\ST@beginsfbl% +\def\verbatim@processline{\ST@wsf{ \the\verbatim@line}}% +\verbatim@start}% +{\ST@endsfbl\@esphack} +% \end{macrocode} +% \end{environment} +% +% \begin{environment}{sageverbatim} +% The opposite of |sagesilent|. This is exactly the same as the verbatim +% environment, except that we include some indentation to be consistent +% with other typeset Sage code. +% \begin{macrocode} +\newenvironment{sageverbatim}{% +\def\verbatim@processline{\hspace{\sagetexindent}\the\verbatim@line\par}% +\verbatim}% +{\endverbatim} +% \end{macrocode} +% \end{environment} +% +% Logically, we now need an environment which neither typesets +% \emph{nor} writes code to the |.sage| file. The verbatim package's +% |comment| environment does that.\\ +% +% \begin{environment}{sageexample} +% Finally, we have an environment which is mostly-but-not-entirely +% verbatim; this is the example environment, which takes input like +% Sage doctests, and prints out the commands verbatim but nicely +% typesets the output of those commands. This and the corresponding +% Python function are originally due to Nicolas M. Thi\'ery. +% \begin{macrocode} +\newcommand{\sageexampleincludetextoutput}{False} +\newenvironment{sageexample}{% + \ST@wsf{% +try:^^J + _st_.current_tex_line = \the\inputlineno^^J + _st_.doctest(\theST@inline, r"""}% + \ST@dodfsetup% + \ST@wdf{Sage example, line \the\inputlineno::^^J}% + \begingroup% + \@bsphack% + \let\do\@makeother\dospecials% + \catcode`\^^M\active% + \def\verbatim@processline{% + \ST@wsf{\the\verbatim@line}% + \ST@wdf{\the\verbatim@line}% + }% + \verbatim@start% +} +{ + \@esphack% + \endgroup% + \ST@wsf{% + """, globals(), locals(), \sageexampleincludetextoutput)^^Jexcept:^^J + _st_.goboom(\the\inputlineno)}% + \ifST@paused% + \mbox{(Sage\TeX{} is paused)}% + \else% + \begin{NoHyper}\ref{@sageinline\theST@inline}\end{NoHyper}% + \@ifundefined{r@@sageinline\theST@inline}{\gdef\ST@rerun{x}}{}% + \fi% + \ST@wdf{}% + \stepcounter{ST@inline}} +% \end{macrocode} +% \changes{v2.2.4}{2010/03/14}{Add first support for +% \texttt{sageexample} environment} +% \end{environment} +% +% +% \begin{environment}{sagecommandline} +% This environment is similar to the |sageexample| environment, but +% typesets the Sage output as text with Python syntax highlighting. +% \begin{macrocode} +\newcommand{\sagecommandlinetextoutput}{True} +\newlength{\sagecommandlineskip} +\setlength{\sagecommandlineskip}{8pt} +\newenvironment{sagecommandline}{% + \ST@wsf{% +try:^^J + _st_.current_tex_line = \the\inputlineno^^J + _st_.commandline(\theST@cmdline, r"""}% + \ST@dodfsetup% + \ST@wdf{Sage commandline, line \the\inputlineno::^^J}% + \begingroup% + \@bsphack% + \let\do\@makeother\dospecials% + \catcode`\^^M\active% + \def\verbatim@processline{% + \ST@wsf{\the\verbatim@line}% + \ST@wdf{\the\verbatim@line}% + }% + \verbatim@start% +} +{ + \@esphack% + \endgroup% + \ST@wsf{% + """, globals(), locals(), \sagecommandlinetextoutput)^^Jexcept:^^J + _st_.goboom(\the\inputlineno)}% + \ifST@paused% + \mbox{(Sage\TeX{} is paused)}% + \else% + \begin{NoHyper}\ref{@sagecmdline\theST@cmdline}\end{NoHyper}% + \@ifundefined{r@@sagecmdline\theST@cmdline}{\gdef\ST@rerun{x}}{}% + \fi% + \ST@wdf{}% + \stepcounter{ST@cmdline}} +% \end{macrocode} +% \end{environment} +% +% \subsubsection{Pausing \ST} +% \label{sec:pausing-sagetex} +% +% How can one have Sage to stop processing \ST output for a little +% while, and then start again? At first I thought I would need some sort +% of ``goto'' statement in Python, but later realized that there's a +% dead simple solution: write triple quotes to the |.sage| file to +% comment out the code. Okay, so this isn't \emph{really} commenting out +% the code; PEP 8 says block comments should use ``|#|'' and Sage will +% read in the ``commented-out'' code as a string literal. For the +% purposes of \ST, I think this is a good decision, though, since (1) +% the pausing mechanism is orthogonal to everything else, which makes it +% easier to not screw up other code, and (2) it will always work. +% +% This illustrates what I really like about \ST: it mixes \LTX and +% Sage/Python, and often what is difficult or impossible in one system +% is trivial in the other. +% +% \begin{macro}{sagetexpause} +% This macro pauses \ST by effectively commenting out code in the +% |.sage| file. When running the corresponding |.sage| file, Sage will +% skip over any commands issued while \ST is paused. +% \begin{macrocode} +\newcommand{\sagetexpause}{\ifST@paused\relax\else +\ST@wsf{print('SageTeX paused on \jobname.tex line \the\inputlineno')^^J"""} +\ST@pausedtrue +\fi} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{sagetexunpause} +% This is the obvious companion to |\sagetexpause|. +% \begin{macrocode} +\newcommand{\sagetexunpause}{\ifST@paused +\ST@wsf{"""^^Jprint('SageTeX unpaused on \jobname.tex line \the\inputlineno')} +\ST@pausedfalse +\fi} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{End-of-document cleanup} +% \label{sec:end-of-doc-cleanup} +% +% We tell the Sage script to write some information to the |.sout| file, +% then check to see if |ST@rerun| ever got defined. If not, all the +% inline formulas and plots worked, so do nothing. We check to see if +% we're paused first, so that we can finish the triple-quoted string in +% the |.sage| file. +% \begin{macrocode} +\AtEndDocument{\ifST@paused +\ST@wsf{"""^^Jprint('SageTeX unpaused at end of \jobname.tex')} +\fi +\ST@wsf{_st_.endofdoc()}% +\@ifundefined{ST@rerun}{}% +% \end{macrocode} +% Otherwise, we issue a warning to tell the user to run Sage on the +% |.sage| file. Part of the reason we do this is that, by using |\ref| +% to pull in the inlines, \LTX will complain about undefined references +% if you haven't run the Sage script---and for many \LTX users, myself +% included, the warning ``there were undefined references'' is a signal +% to run \LTX again. But to fix these particular undefined references, +% you need to run \emph{Sage}. We also suppress file-not-found errors +% for graphics files, and need to tell the user what to do about that. +% +% At any rate, we tell the user to run Sage if it's necessary. +% \begin{macrocode} +{\typeout{*********************************************************************} +\PackageWarningNoLine{sagetex}{there were undefined Sage formulas and/or +plots.^^JRun Sage on \jobname.sagetex.sage, and then run LaTeX on \jobname.tex +again}} +\typeout{*********************************************************************}} +% \end{macrocode} +% +% +% \subsection{The Python module} +% \label{sec:py-file} +% +% \iffalse +% Hey, docstrip! Stop putting code into the .sty file, and start +% putting it into the .py file. +% +%<*python> +% Thanks. +% \fi +% +% The style file writes things to the |.sage| file and reads them from +% the |.sout| file. The Python module provides functions that help +% produce the |.sout| file from the |.sage| file. +% +% \paragraph{A note on Python and \textsf{Docstrip}} There is one tiny +% potential source of confusion when documenting Python code with +% \textsf{Docstrip}: the percent sign. If you have a long line of Python +% code which includes a percent sign for string formatting and you break +% the line with a backslash and begin the next line with a percent sign, +% that line \emph{will not} be written to the output file. This is only +% a problem if you \emph{begin} the line with a (single) percent sign; +% there are no troubles otherwise.\\ +% +% On to the code. Munge the version string (which we get from +% \texttt{sagetex.dtx}) to extract what we want, then import what we +% need: +% \begin{macrocode} +pyversion = ' '.join(__version__.strip('[').split()[0:2]) +from sage.misc.latex import latex +from sage.repl.preparse import preparse +import sys +import os +import os.path +import hashlib +import traceback +import subprocess +import shutil +import re +from collections import defaultdict +% \end{macrocode} +% Define an exception class for version mismatches. I suppose I could +% just use |ValueError|, but this is easy enough: +% \begin{macrocode} +class VersionError(Exception): + pass +% \end{macrocode} +% Sometimes our macros that write things to the |.sout| file get +% evaluated twice, most commonly in the ``fancy'' AMS environments such +% as |align| and |multline|. So we need to keep track of the counters +% we've seen so we don't write labels to the |.sout| file more than +% once. We have more than one kind of label, so a dictionary is the +% natural way to store the counters we've seen for each kind of label. +% For convenience let's make a dictionary subclass for which (1) values +% default to $-1$, and (2) there's an |increment(key)| function that just +% increments the value corresponding to the key. +% \begin{macrocode} +class MyDict(defaultdict): + def __init__(self, *args, **kwargs): + defaultdict.__init__(self, *args, **kwargs) + self.default_factory = lambda: -1 + + def increment(self, key): + self[key] = self[key] + 1 +% \end{macrocode} +% Here's a helper function used by |doctest|; it works ike |j.join(xs)|, +% but ensures exactly one copy of |j| between the strings in |xs|. +% Intended for |j| to be a single character, particularly newline so +% that you can join things with no extra blank lines. +% \begin{macrocode} +def joinone(j, xs_): + if len(xs_) >= 2: + xs = ([xs_[0].rstrip(j)] + + [x.strip(j) for x in xs_[1:-1]] + + [xs_[-1].lstrip(j)]) + else: + xs = xs_ + return j.join(xs) +% \end{macrocode} +% Another helper, used by |commandline| (and maybe, someday, +% |doctest|?). In each line, we look for a possibly empty sequence of +% spaces followed by a non-whitespace character, so we can distinguish +% between whitespace-only lines (which we ignore) and lines that have no +% leading spaces. +% +% One tiny possible problem: you might have a line of only, say, two +% spaces, but perhaps the ``real'' lines all start with at least three +% spaces. Then you would, for that line, do |line[2:]|. That seems like +% it might raise an error, since the line only has indices 0 and 1, but +% Python's indexing handles this perfectly: in that case, |line[2:]| +% will be the empty string, which is fine for our purposes. +% \begin{macrocode} +def strip_common_leading_spaces(s): + lines = s.splitlines() + lead = min(m.end() for m in + [re.match(' *\S', line) for line in lines] + if m is not None) - 1 + return '\n'.join(line[lead:] for line in lines) +% \end{macrocode} +% +% \subsubsection{The \texttt{SageTeXProcessor} class} +% \label{sec:sagetexprocessorclass} +% +% The star of the show, as it were. We define a |SageTeXProcessor| class +% so that it's a bit easier to carry around internal state. We used to +% just have some global variables and a bunch of functions, but this +% seems a bit nicer and easier. +% \begin{macrocode} +class SageTeXProcessor(): +% \end{macrocode} +% If the original |.tex| file has spaces in its name, the |\jobname| +% we get is surrounded by double quotes, so fix that. Technically, it +% is possible to have double quotes in a legitimate filename, but +% dealing with that sort of quoting is +% \href{http://tug.org/pipermail/xetex/2006-August/004712.html}{unpleasant}. +% And yes, we're ignoring the possibility of tabs and other whitespace +% in the filename. Patches for handling pathological filenames welcome. +% +% \changes{v2.3.1}{2011/02/02}{Handle filenames with +% spaces in SageTeXProcessor and sagecommandline env.} +% \changes{v2.3.2}{2012/01/13}{Improve version mismatch check. Fixes +% trac ticket 8035.} +% \begin{macrocode} + def __init__(self, jobname, version=None, version_check=True): + if version != pyversion: + errstr = """versions of .sty and .py files do not match. +{0}.sagetex.sage was generated by sagetex.sty version "{1}", but +is being processed by sagetex.py version "{2}". +Please make sure that TeX is using the sagetex.sty +from your current version of Sage; see +http://doc.sagemath.org/html/en/tutorial/sagetex.html.""".format(jobname, + version, pyversion) + if version_check: + raise VersionError(errstr) + else: + print('**** WARNING! Skipping version check for .sty and .py files, and') + print(errstr) + if ' ' in jobname: + jobname = jobname.strip('"') + self.progress('Processing Sage code for {0}.tex...'.format(jobname)) + self.didinitplot = False + self.useimagemagick = False + self.useepstopdf = False + self.plotdir = 'sage-plots-for-' + jobname + '.tex' + self.filename = jobname + self.name = os.path.splitext(jobname)[0] + autogenstr = """% This file was *autogenerated* from {0}.sagetex.sage with + % sagetex.py version {1}\n""".format(self.name, version) +% \end{macrocode} +% Don't remove the space before the percent sign above! +% +% \LTX environments such as |align| evaluate their arguments twice after +% doing |\savecounters@|, so if you do |\sage| inside such an environment, +% it will result in two labels with the same name in the |.sout| file and +% the user sees a warning when typesetting. So we keep track of the +% largest label we've seen so that we don't write two labels with the same +% name. +% \begin{macrocode} + self.max_counter_seen = MyDict() +% \end{macrocode} +% Open a |.sout.tmp| file and write all our output to that. Then, when +% we're done, we move that to |.sout|. The ``autogenerated'' line is +% basically the same as the lines that get put at the top of preparsed +% Sage files; we are automatically generating a file with Sage, so it +% seems reasonable to add it. Add in the version to help debugging +% version mismatch problems. +% \begin{macrocode} + self.souttmp = open(self.filename + '.sagetex.sout.tmp', 'w') + self.souttmp.write(autogenstr) +% \end{macrocode} +% In addition to the |.sout| file, the |sagecommandline| also needs a +% |.scmd| file. As before, we use a |.scmd.tmp| file and rename it +% later on. We store the position so that |commandline| can tell the +% |listings| package what lines in the |.scmd| file to pull in. +% \begin{macrocode} + self.scmdtmp = open(self.filename + '.sagetex.scmd.tmp', 'w') + self.scmdtmp.write(autogenstr) + self.scmdpos = 3 +% \end{macrocode} +% +% \begin{macro}{progress} +% This function just prints stuff. It allows us to not print a +% linebreak, so you can get ``|start...|'' (little time spent +% processing) ``|end|'' on one line. +% \begin{macrocode} + def progress(self, t,linebreak=True): + if linebreak: + print(t) + else: + sys.stdout.write(t) + sys.stdout.flush() +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{initplot} +% \phantomsection\label{initplot} +% We only want to create the plots directory if the user actually plots +% something. This function creates the directory and sets the +% |didinitplot| flag after doing so. We make a directory based on the +% \LTX file being processed so that if there are multiple |.tex| files +% in a directory, we don't overwrite plots from another file. +% \begin{macrocode} + def initplot(self): + self.progress('Initializing plots directory') +% \end{macrocode} +% We hard-code the |.tex| extension, which is fine in the overwhelming +% majority of cases, although it does cause minor confusion when +% building the documentation. If it turns out lots of people use, say, a +% |ltx| extension or whatever, We could find out the correct extension, +% but it would involve a lot of irritating mucking around---on +% |comp.text.tex|, the best solution I found for finding the file +% extension is to look through the |.log| file. (Although see the +% \texttt{currfile} package.) +% \begin{macrocode} + if os.path.isdir(self.plotdir): + shutil.rmtree(self.plotdir) + os.mkdir(self.plotdir) + self.didinitplot = True +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{inline} +% \phantomsection\label{inlinefn} +% This function works with |\sage| from the style file (see +% \autoref{sec:sagemacro}) to put Sage output into your \LTX file. +% Usually, when you use |\label|, it writes a line such as +% \begin{center} +% |\newlabel{labelname}{{section number}{page number}}| +% \end{center} +% to the |.aux| file. When you use the |hyperref| package, there are +% more fields in the second argument, but the first two fields are the +% same. The |\ref| command just pulls in what's in the first field of +% the second argument, so we can hijack this mechanism for our own +% nefarious purposes. The function writes a |\newlabel| line with a +% label made from a counter and the text from running Sage on |s|. +% +% When the user does |\sage| inside certain displayed math environments +% (|align| is the most common culprit) this function will get called +% twice with exactly the same arguments. We check to see what labels +% we've seen and immediately bail if we've written this label before. +% +% The |labelname| defaults to the the name used by the usual |\sage| +% inline macro, but this function is also used by the |sagecommandline| +% environment. It's important to keep the corresponding labels separate, +% because |\sage| macros often (for example) appear inside math mode, +% and the labels from |sagecommandline| contain a |lstlistings| +% environment---pulling such an environment into math mode produces +% strange, unrecoverable errors, and if you can't typeset your file, you +% can't produce an updated |.sagetex.sage| file to run Sage on to +% produce a reasonable |.sagetext.sout| file that will fix the label +% problem. So it works much better to use distinct labels for such +% things. +% \changes{v2.3.3}{2012/01/16}{check label name when comparing against +% maximum counter seen; trac ticket 12267} +% +% We print out the line number so if something goes wrong, the user can +% more easily track down the offending |\sage| command in the source +% file. +% +% That's a lot of explanation for a short function: +% \begin{macrocode} + def inline(self, counter, s, labelname='sageinline'): + if counter <= self.max_counter_seen[labelname]: + return + else: + self.max_counter_seen.increment(labelname) + if labelname == 'sageinline': + self.progress('Inline formula {0} (line {1})'.format(counter, self.current_tex_line)) + elif labelname == 'sagecmdline': + pass # output message already printed + else: + raise ValueError('inline() got a bad labelname "{0}"'.format(labelname)) + self.souttmp.write(r'\newlabel{@' + labelname + str(counter) + + '}{{%\n' + s.rstrip() + '}{}{}{}{}}\n') +% \end{macrocode} +% We are using five fields, just like |hyperref| does, because that +% works whether or not |hyperref| is loaded. Using two fields, as in +% plain \LTX, doesn't work if |hyperref| is loaded. +% \end{macro} +% +% \begin{macro}{savecmd} +% Analogous to |inline|, this method saves the input string |s| to the +% |souttmp| file. It returns the first and last line of the +% newly-added output so that |commandline| can tell the |listings| +% package where to get stuff. +% \begin{macrocode} + def savecmd(self, s): + self.scmdtmp.write(s.rstrip() + "\n") + begin = self.scmdpos + end = begin + len(s.splitlines()) - 1 + self.scmdpos = end + 1 + return begin, end +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{blockbegin} +% \begin{macro}{blockend} +% \phantomsection\label{blocksbeginend} +% This function and its companion used to write stuff to the |.sout| +% file, but now they just update the user on our progress evaluating a +% code block. The verbatim-like environments of +% \autoref{sec:verbatim-envs} use these functions. +% \begin{macrocode} + def blockbegin(self): + self.progress('Code block (line {}) begin...'.format(self.current_tex_line), False) + def blockend(self): + self.progress('end') +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{splitsagecmds} +% Given a string |s| of doctest-like Sage code, this function returns +% a list of tuples |(i, j, cmd)|, where |cmd| is a string representing +% a Sage command, with the initial prompt and continuation lines +% stripped, |i| is the position in |s| where |cmd| starts, and |j| is +% the starting position in |s| of the purported output from the +% command that was included in |s|. +% +% This is used by |doctest| and |commandline|, below. +% +% For example, this turns the string +% \begin{quote} +% |'''|\\ +% | sage: 1+1|\\ +% | 2|\\ +% | sage: y = 1729|\\ +% | sage: 10^3 + 9^3 == 12^3 + 1^3 == y|\\ +% | sage: gcd(9999999,|\\ +% | ....: 123456)|\\ +% | 3|\\ +% | sage: factor(x^2 + 2*x + 1)|\\ +% | (x + 1)^2|\\ +% |'''| +% \end{quote} +% into +% \begin{quote} +% |[(0, 18, '1+1'),|\\ +% | (28, 51, 'y = 1729'),|\\ +% | (51, 95, '10^3 + 9^3 == 12^3 + 1^3 == y'),|\\ +% | (95, 144, 'gcd(9999999,\n123456)'),|\\ +% | (154, 190, 'factor(x^2 + 2*x + 1)')]| +% \end{quote} +% You can reconstruct the sequence of commands and their output with +% something like +% \begin{quote} +% |splitup = split_sage_cmds(s)|\\ +% |oldout = splitup[0][1]|\\ +% |print('=' * 50)|\\ +% |print('==== Command:')|\\ +% |print(s[splitup[0][0]:oldout])|\\ +% |for start, out, _ in splitup[1:]:|\\ +% | print('==== Given output:')|\\ +% | print(s[oldout:start])|\\ +% | print('=' * 50)|\\ +% | print('==== Command:')|\\ +% | print(s[start:out])|\\ +% | oldout = out|\\ +% |print('==== Given output:')|\\ +% |print(s[oldout:])| +% \end{quote} +% \begin{macrocode} + def split_sage_cmds(self, s): + prompt = '\n' + r'\s*sage: ' + oldcont = r'\s*\.\.\.' + cont = r'\s*\.\.\.\.: ' +% \end{macrocode} +% Prepending a newline to |s| ensures that the list from |re.split()| +% begins with something we can ignore---and so that the |re.split()| +% returns $n+1$ groups for $n$ matches, so therefore |split| and +% |starts| match up. +% \begin{macrocode} + split = re.split(prompt, '\n' + s)[1:] + starts = [m.start() - 1 for m in re.finditer(prompt, '\n' + s)] +% \end{macrocode} +% The prepended newline messes up the first element of |starts|, fix +% that: +% \begin{macrocode} + starts[0] = re.search(prompt, s).start() +% \end{macrocode} +% Now find where the outputs start. We need this because |doctest()| may +% or may not print the outputs. The idea is: for each |start|ing +% position, advance over the prompt that we know is there, then look for +% the rightmost continuation marker between the current prompt and the +% next one, and \emph{then} look for the newline following that. That +% position is where the output begins. +% \begin{macrocode} + outputs = [] + for i, j in zip(starts, starts[1:] + [len(s)]): + k = i + re.match(prompt, s[i:j]).end() + try: + k += [m.end() for m in re.finditer(cont, s[k:j])][-1] + except IndexError: + pass + end = s.find('\n', k) + outputs.append(end) +% \end{macrocode} +% Now we take each command group, split it up, and look for all the +% continuation lines. We append those, stripping off the continuation +% marks. We also do some error checking so that users with documents +% that use the old |...| continuation marks get a reasonable error +% message. +% \begin{macrocode} + ret = [] + for start, end, g in zip(starts, outputs, split): + lines = g.splitlines() + cmd = lines[:1] + for line in lines[1:]: + has_old_cont = re.match(oldcont, line) + has_cont = re.match(cont, line) + if has_old_cont and not has_cont: + raise SyntaxError(""" SageTeX no longer supports "..." for line continuation in sagexample and +sagecommandline environments. Use "....:", which matches what the Sage +interpreter uses. See the documentation and example file in +SAGE_ROOT/local/share/doc/sagetex.""") + if has_cont: + cmd.append(line[has_cont.end():]) + ret.append((start, end, '\n'.join(cmd))) + return ret +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{doctest} +% This function handles the |sageexample| environment, which typesets +% Sage code and its output. We call it |doctest| because the format is +% just like that for doctests in the Sage library. +% +% The idea is: +% \begin{enumerate} +% \item Get the literal text for each command, wrap that in +% |SaveVerbatim|, write that (possibly with its associated output from +% the |.tex| file) to the |sout| file. +% \item Accumulate a corresponding |UseVerbatim| and typeset output so +% that we can call |inline()| at the end and pull in all this stuff. +% \item For the output: try to |eval()| the processed command (the one +% with the prompts and continuation marks stripped). If that succeeds, +% we run |latex()| on it and display that below the verbatim text from +% above. If that fails, it's because you have a statement and not an +% expression---there's no output from such a thing (well, none that we +% can capture, anyway) so no need to typeset output. +% \end{enumerate} +% \begin{macrocode} + def doctest(self, counter, s, globals, locals, include_text_output): + self.progress('Sage example {0} (line {1})'.format(counter, self.current_tex_line)) + splitup = self.split_sage_cmds(s) + tex_strs = [] + for i in range(len(splitup)): + boxname = '@sageinline{}-code{}'.format(counter, i) + to_tmp = [r'\begin{SaveVerbatim}{' + boxname + '}', + s[splitup[i][0]:splitup[i][1]]] + if include_text_output: + try: + to_tmp.append(s[splitup[i][1]:splitup[i+1][0]]) + except IndexError: + to_tmp.append(s[splitup[i][1]:]) + to_tmp.append('\\end{SaveVerbatim}\n') + self.souttmp.write(joinone('\n', to_tmp)) +% \end{macrocode} +% Now we build up something that we can send to |inline()|, which will +% pull it into the document using its usual label mechanism. +% +% The verbatim stuff seems to end with a bit of vertical space, so don't +% start the displaymath environment with unnecessary vertical +% space---the displayskip stuff is from \S 11.5 of Herbert Vo\ss's +% ``\href{http://www.ctan.org/tex-archive/info/math/voss/mathmode/}{Math +% Mode}''. +% \begin{macrocode} + tex_strs.append(r'\UseVerbatim{' + boxname + '}') + try: + result = eval(preparse(splitup[i][2]), globals, locals) + tex_strs += [r'\abovedisplayskip=0pt plus 3pt ', + r'\abovedisplayshortskip=0pt plus 3pt ', + r'\begin{displaymath}', + latex(result), + r' \end{displaymath}'] + except SyntaxError: + exec(preparse(splitup[i][2]), globals, locals) + self.inline(counter, '\n'.join(tex_strs)) +% \end{macrocode} +% \changes{v2.2.5}{2010/03/25}{Fix up spacing in sageexample displaymath envs} +% \changes{v3.0}{2015/08/26}{sageexample and sagecommandline now require +% ``...:'' for continuation lines, not ``...''; matches the actual +% Sage interpreter} +% \end{macro} +% +% \begin{macro}{commandline} +% This function handles the |commandline| environment, which typesets +% Sage code, computes its output, and typesets that too. This is very +% similar to |doctest| and I hope to someday combine them into one. +% +% Even if I can't refactor these two functions (and their associated +% environments) into one, I would like to eliminate the |.scmd| file +% that this function uses, since exactly the same bits of Sage code +% get written to both the |.scmd| file and the |_doctest.sage| file. +% The reason this isn't trivial is because we need to keep track of +% which line number we're on so that we can give the |listings| +% package a start and end line to extract, and right now the +% |_doctest.sage| file is written to by \LaTeX{} and we can't track +% the line number. +% +% In any case, here's what we do: after splitting up the provided +% string using |split_sage_cmds|, we iterate over each of the +% commands and: +% \begin{enumerate} +% \item Put the original input command into the |.scmd| file with +% |savecmd|. +% \item Use the |begin| and |end| line numbers to append a +% |lstinputlisting| command to the \TeX{} commands we'll eventually +% hand off to |inline|. +% \item Evaluate the command using |eval()| or |exec|, as necessary. +% If we're doing plain text format, we send the output to the +% |.scmd| file and add a \TeX{} command to pull that back in---if we +% need typeset output, then we just hit the output with |latex()| +% and add that to the list of \TeX{} commands. +% \end{enumerate} +% Observe that we detect spaces in the filename and quote that for +% \TeX{} if we need to. +% \begin{macrocode} + def commandline(self, counter, s, globals, locals, text_output): + self.progress('Sage commandline {0} (line {1})'.format(counter, self.current_tex_line)) + scmd_fn = self.name + '.sagetex.scmd' + if ' ' in scmd_fn: + scmd_fn = '"{}"'.format(scmd_fn) + + splitup = self.split_sage_cmds(s) + skip = r'\vspace{\sagecommandlineskip}' + tex_strs = [skip] + lstinput = r'\lstinputlisting[firstline={0},lastline={1},firstnumber={2},style=SageInput{escape}]{{{3}}}' + for i in range(len(splitup)): + orig_input = s[splitup[i][0]:splitup[i][1]] + begin, end = self.savecmd(strip_common_leading_spaces(orig_input.strip('\n'))) + if '#@' in orig_input: + escapeoption = ',escapeinside={\\#@}{\\^^M}' + else: + escapeoption = '' + tex_strs.append(lstinput.format(begin, end, begin - 2, scmd_fn, escape=escapeoption)) + try: + result = eval(preparse(splitup[i][2]), globals, locals) + if text_output: + begin, end = self.savecmd(str(result)) + tex_strs.append(lstinput.format(begin, end, begin - 2, scmd_fn, escape='')) + else: + tex_strs.append(r'\begin{displaymath}' + + latex(result) + + r'\end{displaymath}') + except SyntaxError: + exec(preparse(splitup[i][2]), globals, locals) + if 'displaymath' not in tex_strs[-1]: + tex_strs.append(skip) + self.inline(counter, '\n'.join(tex_strs), labelname='sagecmdline') +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{plot} +% \phantomsection\label{plotfn} +% I hope it's obvious that this function does plotting. It's the Python +% counterpart of |\ST@sageplot| described in \autoref{sec:sageplotmacro}. As +% mentioned in the |\sageplot| code, we're taking advantage of two +% things: first, that \LTX doesn't treat commas and spaces in macro +% arguments specially, and second, that Python (and Sage plotting +% functions) has nice support for keyword arguments. The |#3| argument +% to |\sageplot| becomes |_p_| and |**kwargs| below. +% \begin{macrocode} + def plot(self, counter, _p_, format='notprovided', **kwargs): + if not self.didinitplot: + self.initplot() + self.progress('Plot {0} (line {1})'.format(counter, self.current_tex_line)) +% \end{macrocode} +% If the user says nothing about file formats, we default to producing +% PDF and EPS. This allows the user to transparently switch between +% using a DVI previewer (which usually automatically updates when the +% DVI changes, and has support for source specials, which makes the +% writing process easier) and making PDFs.\footnote{Yes, there's +% \texttt{pdfsync}, but full support for that is still rare in Linux, so +% producing EPS and PDF is the best solution for now.} +% \begin{macrocode} + if format == 'notprovided': + formats = ['eps', 'pdf'] + else: + formats = [format] + for fmt in formats: +% \end{macrocode} +% If we're making a PDF and have been told to use |epstopdf|, do so, +% then skip the rest of the loop. +% \begin{macrocode} + if fmt == 'pdf' and self.useepstopdf: + epsfile = os.path.join(self.plotdir, 'plot-{0}.eps'.format(counter)) + self.progress('Calling epstopdf to convert plot-{0}.eps to PDF'.format( + counter)) + subprocess.check_call(['epstopdf', epsfile]) + continue +% \end{macrocode} +% Some plot objects (mostly 3-D plots) do not support saving to EPS or +% PDF files (yet), but everything can be saved to a PNG file. For the +% user's convenience, we catch the error when we run into such an +% object, save it to a PNG file, then exit the loop. +% \begin{macrocode} + plotfilename = os.path.join(self.plotdir, 'plot-{0}.{1}'.format(counter, fmt)) + try: + _p_.save(filename=plotfilename, **kwargs) + except ValueError as inst: + if re.match('filetype .*not supported by save', str(inst)): + newfilename = plotfilename[:-3] + 'png' + print(' saving {0} failed; saving to {1} instead.'.format( + plotfilename, newfilename)) + _p_.save(filename=newfilename, **kwargs) + break + else: + raise +% \end{macrocode} +% If the user provides a format \emph{and} specifies the |imagemagick| +% option, we try to convert the newly-created file into EPS format. +% \begin{macrocode} + if format != 'notprovided' and self.useimagemagick: + self.progress('Calling Imagemagick to convert plot-{0}.{1} to EPS'.format( + counter, format)) + self.toeps(counter, format) +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{toeps} +% This function calls the Imagmagick utility |convert| to, well, convert +% something into EPS format. This gets called when the user has +% requested the ``|imagemagick|'' option to the \ST\ style file and is +% making a graphic file with a nondefault extension. +% \begin{macrocode} + def toeps(self, counter, ext): + subprocess.check_call(['convert',\ + '{0}/plot-{1}.{2}'.format(self.plotdir, counter, ext), \ + '{0}/plot-{1}.eps'.format(self.plotdir, counter)]) +% \end{macrocode} +% We are blindly assuming that the |convert| command exists and will do +% the conversion for us; the |check_call| function raises an exception +% which, since all these calls get wrapped in try/excepts in the |.sage| +% file, should result in a reasonable error message if something strange +% happens. +% \end{macro} +% +% \begin{macro}{goboom} +% \phantomsection\label{macro:goboom} +% When a chunk of Sage code blows up, this function bears the bad news +% to the user. Normally in Python the traceback is good enough for +% this, but in this case, we start with a |.sage| file (which is +% autogenerated) which itself autogenerates a |.py| file---and the +% tracebacks the user sees refer to that file, whose line numbers are +% basically useless. We want to tell them where in the \LTX file +% things went bad, so we do that, give them the traceback, and exit +% after removing the |.sout.tmp| and |.scmd.tmp| file. +% \begin{macrocode} + def goboom(self, line): + print('\n**** Error in Sage code on line {0} of {1}.tex! Traceback\ + follows.'.format(line, self.filename)) + traceback.print_exc() + print('\n**** Running Sage on {0}.sage failed! Fix {0}.tex and try\ + again.'.format(self.filename)) + self.souttmp.close() + os.remove(self.filename + '.sagetex.sout.tmp') + self.scmdtmp.close() + os.remove(self.filename + '.sagetex.scmd.tmp') + sys.exit(int(1)) +% \end{macrocode} +% We use |int(1)| above to make sure |sys.exit| sees a Python integer; +% see +% \href{http://trac.sagemath.org/sage_trac/ticket/2861#comment:5}{ticket +% \#2861}. +% \changes{v2.0.2}{2008/04/21}{Make sure sys.exit sees a Python integer} +% \end{macro} +% +% \begin{macro}{endofdoc} +% \phantomsection\label{macro:endofdoc} +% When we're done processing, we have some cleanup tasks. We +% want to put the MD5 sum of the |.sage| file that produced the |.sout| +% file we're about to write into the |.sout| file, so that external +% programs that build \LTX documents can determine if they need to call Sage +% to update the |.sout| file. But there is a problem: we write line +% numbers to the |.sage| file so that we can provide useful error +% messages---but that means that adding non-\ST text to your +% source file will change the MD5 sum, and your program will think it +% needs to rerun Sage even though none of the actual \ST macros +% changed. +% +% How do we include line numbers for our error messages but still allow +% a program to discover a ``genuine'' change to the |.sage| file? +% +% The answer is to only find the MD5 sum of \emph{part} of the |.sage| +% file. By design, the source file line numbers only appear in (1) calls +% to |goboom|, (2) lines with |_st_.current_tex_line|, and (3) +% pause/unpause lines, so we will strip those lines out. What we do +% below is exactly equivalent to running +% \begin{center} +% \verb+egrep -v '^( _st_.goboom| ?_st_.current_tex_line|print(.SageT)' filename.sage | md5sum+ +% \end{center} +% in a shell. The included |run-sagetex-if-necessary| uses this +% mechanism to, well, only run Sage when necessary; see +% \autoref{sec:run-sagetex-if-necessary}. +% \begin{macrocode} + def endofdoc(self): + sagef = open(self.filename + '.sagetex.sage', 'r') + m = hashlib.md5() + for line in sagef: + if not line.startswith((" _st_.goboom", + "print('SageT", + "_st_.current_tex_line", + " _st_.current_tex_line")): + m.update(bytearray(line,'utf8')) +% \end{macrocode} +% (The |current_tex_line| thing appears twice because it may appear +% indented one space or not, depending on whether it's used before +% |blockbegin| or not.) +% \begin{macrocode} + s = '%' + m.hexdigest() + '% md5sum of corresponding .sage file\ + (minus "goboom", "current_tex_line", and pause/unpause lines)\n' + self.souttmp.write(s) + self.scmdtmp.write(s) +% \end{macrocode} +% Now, we do issue warnings to run Sage on the |.sage| file and an +% external program might look for those to detect the need to rerun +% Sage, but those warnings do not quite capture all situations. (If +% you've already produced the |.sout| file and change a |\sage| call, no +% warning will be issued since all the |\ref|s find a |\newlabel|.) +% Anyway, I think it's easier to grab an MD5 sum out of the end of the +% file than parse the output from running |latex| on your file. (The +% regular expression |^%[0-9a-f]{32}%| will find the MD5 sum. Note that +% there are percent signs on each side of the hex string.) +% +% Now we are done with the |.sout.tmp| file. Close it, rename it, and +% tell the user we're done. +% \begin{macrocode} + self.souttmp.close() + os.rename(self.filename + '.sagetex.sout.tmp', self.filename + '.sagetex.sout') + self.scmdtmp.close() + os.rename(self.filename + '.sagetex.scmd.tmp', self.filename + '.sagetex.scmd') + self.progress('Sage processing complete. Run LaTeX on {0}.tex again.'.format( + self.filename)) +% \end{macrocode} +% % \changes{v2.1.1}{2009/05/14}{Fix bug in finding md5 sum introduced by +% pause facility} +% \end{macro} + +% \endinput +% +% Local Variables: +% mode: doctex +% TeX-master: "sagetex.dtx" +% End: -- cgit v1.2.3