The SageTEX package

The SageTEX package
The SageTEX package∗
Dan Drake and others†
August 27, 2010
1
Introduction
1.1
Literate Programming
Why should the Haskell and R folks have all the fun? Literate Haskell is a popular
way to mix Haskell source code and LATEX documents. (Actually any kind of
text or document, but here we’re concerned only with LATEX.) You can even
embed Haskell code in your document that writes part of your document for you.
Similarly, the R statistical computing environment includes Sweave, which lets
you do the same thing with R code and LATEX.
The SageTEX package allows you to do (roughly) the same thing with the Sage
mathematics software suite (see http://sagemath.org) and LATEX. (If you know
how to write literate Haskell: the \eval command corresponds to \sage, and the
code environment to the sageblock environment.) As a simple example, imagine
in your document you are writing about how to count license plates with three
letters and three digits. With this package, you can write something like this:
There are $26$ choices for each letter, and $10$ choices for
each digit, for a total of $26^3 \cdot 10^3 =
\sage{26^3*10^3}$ license plates.
and it will produce
There are 26 choices for each letter, and 10 choices for each digit, for
a total of 263 · 103 = 17576000 license plates.
The great thing is, you don’t have to do the multiplication. Sage does it for you.
This process mirrors one of the great aspects of LATEX: when writing a LATEX
document, you can concentrate on the logical structure of the document and trust
LATEX and its army of packages to deal with the presentation and typesetting.
Similarly, with SageTEX, you can concentrate on the mathematical structure (“I
need the product of 263 and 103 ”) and let Sage deal with the base-10 presentation
of the number.
∗ This
document corresponds to SageTEX v2.2.5, dated 2010/03/25.
website: mathsci.kaist.ac.kr/∼drake/.
† Author’s
1
1.2
Adding plots
A less trivial, and perhaps more useful example is plotting. You can include a plot
of the sine curve without manually producing a plot, saving an EPS or PDF file,
and doing the \includegraphics business with the correct filename yourself. If
you write this:
Here is a lovely graph of the sine curve:
\sageplot{plot(sin(x), x, 0, 2*pi)}
in your LATEX file, it produces
Here is a lovely graph of the sine curve:
1
0.5
1
2
4
3
5
6
-0.5
-1
Again, you need only worry about the logical/mathematical structure of your
document (“I need a plot of the sine curve over the interval [0, 2π] here”), while
SageTEX takes care of the gritty details of producing the file and sourcing it into
your document.
But \sageplot isn’t magic I just tried to convince you that SageTEX makes
putting nice graphics into your document very easy; let me turn around and warn
you that using graphics well is not easy, and no LATEX package or Python script
will ever make it easy. What SageTEX does is make it easy to use Sage to create
graphics; it doesn’t magically make your graphics good, appropriate, or useful.
(For instance, look at the sine plot above—I would say that a truly lovely plot
of the sine curve would not mark integer points on the x-axis, but rather π/2, π,
3π/2, and 2π.)
Till Tantau has some good commentary on the use of graphics in section 6 of
the pgf manual. You should always give careful thought and attention to creating
graphics for your document; I have in mind that a good workflow for using SageTEX
for plotting is something like this:
2
1. Figure out what sort of graphic you need to communicate your ideas or
information.
2. Fiddle around in Sage until you get a graphics object and set of options that
produce the graphic you need.
3. Copy those commands and options into SageTEX commands in your LATEX
document.
The SageTEX package’s plotting capabilities don’t help you find those Sage
commands to make your lovely plot, but they do eliminate the need to muck
around with saving the result to a file, remembering the filename, including it into
your document, and so on. In section 3, we will see what what we can do with
SageTEX.
1.3
Sage Command Line
Yet another way to use SageTEX is as a pretty-printing command line, this is
provided by the sagecommandline environment. For example, you can write
\begin{sagecommandline}
sage: 1+1
sage: ( x^2+2*x+1 ).factor()
(x + 1)^2
\end{sagecommandline}
# difficult
#@\label{square}
As you can see, you have a choice of either explicitly providing the Sage output
(in which case it will be turned into a doctest), or leaving it up to the computer
to fill in the blanks. Moreover, any sage comment that starts with a “at” sign
is escaped to LATEX. In particular, you can use \label to mark line numbers in
order to \reference and \pagereference them as usual. The output looks like
this:
sage : 1+1
2
sage : ( x ^2+2* x +1 ) . factor ()
( x + 1) ^2
# difficult
1
2
3
4
and the label is on Line 3, Page 3. If you prefer to typeset the output in LATEX,
you can set
\renewcommand{\sagecommandlinetextoutput}{False}
which produces
sage : var ( ’a , b , c ’) ;
sage : ( a * x ^2+ b * x + c ) . solve ( x )
#
"
√
√
−b − −4 ac + b2
−b + −4 ac + b2
,x =
x=
2a
2a
3
5
6
The Sage input and output is typeset using the listings package with the
styles SageInput and SageOutput, respectively. If you don’t like the defaults
you can change them. It is recommended to derive from DefaultSageInput and
DefaultSageOutput, for example
\lstdefinestyle{SageInput}{style=DefaultSageInput,
basicstyle={\color{red}}}
\lstdefinestyle{SageOutput}{style=DefaultSageOutput,
basicstyle={\color{green}}}
makes things overly colorful:
sage : p i . n ( 1 0 0 )
3.1415926535897932384626433833
2
Installation
SageTEX needs two parts to work: a Python module known to Sage, and a LATEX
package known to TEX. These two parts need to come from the same version
of SageTEX to guarantee that everything works properly. As of Sage version
4.3.1, SageTEX comes included with Sage, so you only need to make sagetex.sty,
the LATEX package, known to TEX. Full details of this are in the Sage Installation guide at sagemath.org/doc/installation/ in the obviously-named section
“Make SageTEX known to TEX”. Here’s a brief summary of how to do that:
• Copy sagetex.sty to the same directory as your document. This always
works, but requires lots of copies of sagetex.sty and is prone to version
skew.
• Copy the directory containing sagetex.sty to your home directory with a
command like
cp -R $SAGE ROOT/local/share/texmf ~/
where $SAGE ROOT is replaced with the location of your Sage installation.
• Use the environment variable TEXINPUTS to tell TEX to search the directory
containing sagetex.sty; in the bash shell, you can do
export TEXINPUTS=$SAGE ROOT/local/share/texmf//:
You should again replace $SAGE ROOT with the location of your Sage installation.
The best method is likely the second; while that does require you to recopy
the files every time you update your copy of Sage, it does not depend on your
shell, so if you use, say, Emacs with AucTEX or some other editor environment,
4
11
12
everything will still work since TEX’s internal path-searching mechanisms can find
sagetex.sty.
Note that along with sagetex.sty, this documentation, an example file, and
other useful scripts are all located in the directory $SAGE ROOT/local/share/texmf.
2.1
SageTEX and TEXLive
SageTEX is included in TEXLive, which is very nice, but because the Python module
and LATEX package for SageTEX need to be synchronized, if you use the LATEX
package from TEXLive and the Python module from Sage, they may not work
together if they are from different versions of SageTEX. Because of this, I strongly
recommend using SageTEX only from what is included with Sage and ignoring
what’s included with TEXLive.
2.2
The noversioncheck option
As of version 2.2.4, SageTEX automatically checks to see if the versions of the
style file and Python module match. This is intended to prevent strange version
mismatch problems, but if you would like to use mismatched sources, you can—at
your peril—give the noversioncheck option when you load the SageTEX package.
Don’t be surprised if things don’t work when you do this.
If you are considering using this option because the Sage script complained and
exited, you really should just get the LATEX and Python modules synchronized.
Every copy of Sage since version 4.3.2 comes with a copy of sagetex.sty that is
matched up to Sage’s baked-in SageTEX support, so you can always use that. See
the SageTEX section of the Sage installation guide.
2.3
Using TEXShop
Starting with version 2.25, TEXShop includes support for SageTEX. If you
move the file sage.engine from ~/Library/TeXShop/Engines/Inactive/Sage
to ~/Library/TeXShop/Engines and put the line
%!TEX TS-program = sage
at the top of your document, then TEXShop will automatically run Sage for you
when compiling your document.
Note that you will need to make sure that LATEX can find sagetex.sty using
any of the methods above. You also might need to edit the sage.engine script
to reflect the location of your Sage installation.
2.4
Other scripts included with SageTEX
SageTEX includes several Python files which may be useful for working with
“SageTEX-ified” documents. The remote-sagetex.py script allows you to use
SageTEX on a computer that doesn’t have Sage installed; see section 5 for more
information.
5
Also included are makestatic.py and extractsagecode.py, which are convenience scripts that you can use after you’ve written your document. See
section 4.4 and section 4.5 for information on using those scripts. The file
sagetexparse.py is a module used by both those scripts. These three files are
independent of SageTEX. If you install from a spkg, these scripts can be found in
$SAGE ROOT/local/share/texmf/.
3
Usage
Let’s begin with a rough description of how SageTEX works. Naturally the very
first step is to put \usepackage{sagetex} in the preamble of your document.
When you use macros from this package and run LATEX on your file, along with
the usual zoo of auxiliary files, a .sage file is written with the same basename as
your document. This is a Sage source file that uses the Python module from this
package and when you run Sage on that file, it will produce a .sout and a .scmd
file. That file contains LATEX code that, when you run LATEX on your source file
again, will pull in all the results of Sage’s computation.
The sagecommandline environment additionally logs the plain sage commands
and output furthermore in a .scmd file.
All you really need to know is that to typeset your document, you need to
run LATEX, then run Sage, then run LATEX again. You can even “run Sage” on a
computer that doesn’t have Sage installed by using the remote-sagetex.py script;
see section 5. Whenever this manual says “run Sage”, you can either directly run
Sage, or use the remote-sagetex.py script.
Also keep in mind that everything you send to Sage is done within one Sage
session. This means you can define variables and reuse them throughout your
LATEX document; if you tell Sage that foo is 12, then anytime afterwards you can
use foo in your Sage code and Sage will remember that it’s 12—just like in a
regular Sage session.
Now that you know that, let’s describe what macros SageTEX provides and
how to use them. If you are the sort of person who can’t be bothered to read
documentation until something goes wrong, you can also just look through the
example.tex file included with this package.1
WARNING! When you run LATEX on a file named hfilename i.tex, the file
hfilename i.sage is created—and will be automatically overwritten if it already
exists. If you keep Sage scripts in the same directory as your SageTEX-ified LATEX
documents, use a different file name!
The final option On a similar note, SageTEX, like many LATEX packages, accepts
the final option. When passed this option, either directly in the \usepackage
line, or from the \documentclass line, SageTEX will not write a .sage file. It will
try to read in the .sout file so that the SageTEX macros can pull in their results.
1 Then again, if you’re such a person, you’re probably not reading this, and are already fiddling
with example.tex. . .
6
However, this will not allow you to have an independent Sage script with the same
basename as your document, since to get the .sout file, you need the .sage file.
3.1
sage
Inline Sage
\sage{hSage codei} takes whatever Sage code you give it, runs Sage’s latex
function on it, and puts the result into your document.
For example, if you do \sage{matrix([[1, 2], [3,4]])^2}, then that macro
will get replaced by
\left(\begin{array}{rr}
7 & 10 \\
15 & 22
\end{array}\right)
in your document—that LATEX code is exactly exactly what you get from doing
latex(matrix([[1, 2], [3,4]])^2)
in Sage.
Note that since LATEX will do macro expansion on whatever you give to \sage,
you can mix LATEX variables and Sage variables! If you have defined the Sage
variable foo to be 12 (using, say, the sageblock environment), then you can do
something like this:
The prime factorization of the current page number plus foo
is $\sage{factor(foo + \thepage)}$.
Here, I’ll do just that right now: the prime factorization of the current page
number plus 12 is 19. (Wrong answer? See footnote.2 ) The \sage command
doesn’t automatically use math mode for its output, so be sure to use dollar signs
or a displayed math environment as appropriate.
\sagestr
\percent
\sagestr{hSage codei} is identical to \sage, but is does not run Sage’s latex
function on the code you give it; it simply runs the Sage code and pulls the result
into your LATEX file. This is useful for calling functions that return LATEX code;
see the example file distributed along with SageTEX for a demonstration of using
this command to easily produce a table.
If you are doing modular arithmetic or string formatting and need a percent
sign in a call to \sage (or \sageplot), you can use \percent. Using a bare
2 Is the above factorization wrong? If the current page number plus 12 is one larger than the
claimed factorization, another Sage/LATEX cycle on this source file should fix it. Why? The first
time you run LATEX on this file, the sine graph isn’t available, so the text where I’ve talked about
the prime factorization is back one page. Then you run Sage, and it creates the sine graph and
does the factorization. When you run LATEX again, the sine graph pushes the text onto the next
page, but it uses the Sage-computed value from the previous page. Meanwhile, the .sage file
has been rewritten with the correct page number, so if you do another Sage/LATEX cycle, you
should get the correct value above. However, in some cases, even that doesn’t work because of
some kind of TEX weirdness in ending the one page a bit short and starting another.
7
percent sign won’t work because LATEX will think you’re starting a comment and
get confused; prefixing the percent sign with a backslash won’t work because then
“\%” will be written to the .sage file and Sage will get confused. The \percent
macro makes everyone happy.
Note that using \percent inside the verbatim-like environments described in
section 3.3 isn’t necessary; a literal “%” inside such an environment will get written, uh, verbatim to the .sage file.
3.2
\sageplot
Graphics and plotting
\sageplot[hltx optsi][hfmti]{hgraphics obj i, hkeyword argsi} plots the given
Sage graphics object and runs an \includegraphics command to put it into
your document. It does not have to actually be a plot of a function; it can be any
Sage graphics object. The options are described in Table 1.
Option
hltx optionsi
hfmti
hgraphics obj i
hkeyword argsi
Description
Any text here is passed directly into the optional arguments (between the square brackets) of
an \includegraphics command. If not specified,
“width=.75\textwidth” will be used.
You can optionally specify a file extension here; Sage
will then try to save the graphics object to a file with
extension fmt. If not specified, SageTEX will save to
EPS and PDF files; if saving to those formats does
not work, SageTEX will save to a PNG file.
A Sage object on which you can call .save() with a
graphics filename.
Any keyword arguments you put here will all be put
into the call to .save().
Table 1: Explanation of options for the \sageplot command.
This setup allows you to control both the Sage side of things, and the LATEX
side. For instance, the command
\sageplot[angle=30, width=5cm]{plot(sin(x), 0, pi), axes=False,
chocolate=True}
will run the following command in Sage:
sage: plot(sin(x), 0, pi).save(filename=autogen, axes=False,
chocolate=True)
Then, in your LATEX file, the following command will be issued automatically:
\includegraphics[angle=30, width=5cm]{autogen}
8
You can specify a file format if you like. This must be the second optional
argument, so you must use empty brackets if you’re not passing anything to
\includegraphics:
\sageplot[][png]{plot(sin(x), x, 0, pi)}
The filename is automatically generated, and unless you specify a format, both
EPS and PDF files will be generated. This allows you to freely switch between
using, say, a DVI viewer (many of which have support for automatic reloading,
source specials and make the writing process easier) and creating PDFs for posting
on the web or emailing to colleagues. SageTEX will fall back to creating a PNG
file for any graphics object that cannot be saved as an EPS or PDF file; this is
useful for three dimensional plot objects, which currently cannot be saved as EPS
or PDF files.
If you ask for, say, a PNG file (or if one is automatically generated for you as
described above), keep in mind that ordinary latex and DVI files have no support
for PNG files; SageTEX detects this and will warn you that it cannot find a suitable
file if using latex.3 If you use pdflatex, there will be no problems because PDF
files can include PNG graphics.
When SageTEX cannot find a graphics file, it inserts this into your document:
??
That’s supposed to resemble the image-not-found graphics used by web browsers
and use the traditional “??” that LATEX uses to indicate missing references.
You needn’t worry about the filenames; they are automatically generated and
will be put into the directory sage-plots-for-filename.tex. You can safely
delete that directory anytime; if SageTEX can’t find the files, it will warn you to
run Sage to regenerate them.
WARNING! When you run Sage on your .sage file, all files in the
sage-plots-for-hfilename i.tex directory will be deleted! Do not put any
files into that directory that you do not want to get automatically deleted.
The epstopdf option One of the graphics-related options supported by
SageTEX is epstopdf. This option causes SageTEX to use the epstopdf command to convert EPS files into PDF files. Like with the imagemagick option,
it doesn’t check to see if the epstopdf command exists or add options: it just
runs the command. This option was motivated by a bug in the matplotlib PDF
backend which caused it to create invalid PDFs. Ideally, this option should never
be necessary; if you do need to use it, file a bug!
This option will eventually be removed, so do not use it.
3 We use a typewriter font here to indicate the executables which produce DVI and PDF files,
respectively, as opposed to “LATEX” which refers to the entire typesetting system.
9
3.2.1
3D plotting
Right now there is, to put it nicely, a bit of tension between the sort of graphics
formats supported by latex and pdflatex, and the graphics formats supported
by Sage’s 3D plotting systems. LATEX is happiest, and produces the best output,
with EPS and PDF files, which are vector formats. Tachyon, Sage’s 3D plotting
system, produces bitmap formats like BMP and PNG.
SageTEX will automatically fall back to saving plot objects in PNG format
if saving to EPS and PDF fails, so it should automatically work with 3D plot
objects. However, since latex does not support PNGs, when using 3D plotting
(and therefore a bitmap format like PNG), SageTEX will always issue a warning
about incompatible graphics if you use latex, provided you’ve processed the .sage
file and the PNG file exists. The only exception is if you’re using the imagemagick
option below.
The imagemagick option As a response to the above issue, the SageTEX package has an imagemagick option. If you specify this option in the preamble of your
document with the usual “\usepackage[imagemagick]{sagetex}”, then when
you are compiling your document using latex, any \sageplot command which
requests a non-default format will cause the SageTEX Python script to convert the
resulting file to EPS using the Imagemagick convert utility. It does this by executing “convert filename.EXT filename.eps” in a subshell. It doesn’t add any
options, check to see if the convert command exists or belongs to Imagemagick—it
just runs the command.
The resulting EPS files are not very high quality, but they will work. This
option is not intended to produce good graphics, but to allow you to see your
graphics when you use latex and DVI files while writing your document.
3.2.2
But that’s not good enough!
The \sageplot command tries to be both flexible and easy to use, but if you are
just not happy with it, you can always do things manually: inside a sagesilent
environment (see the next section) you could do
your special commands
x = your graphics object
x.save(filename=myspecialfile.ext, options, etc)
and then, in your source file, do your own \includegraphics command. The
SageTEX package gives you full access to Sage and Python and doesn’t turn off
anything in LATEX, so you can always do things manually.
3.3
Verbatim-like environments
The SageTEX package provides several environments for typesetting and executing
blocks of Sage code.
10
sageblock
Any text between \begin{sageblock} and \end{sageblock} will be typeset
into your file, and also written into the .sage file for execution. This means you
can do something like this:
\begin{sageblock}
var(’x’)
f(x) = sin(x) - 1
g(x) = log(x)
h(x) = diff(f(x) * g(x), x)
\end{sageblock}
and then anytime later write in your source file
We have $h(2) = \sage{h(2)}$, where $h$ is the derivative of
the product of $f$ and $g$.
and the \sage call will get correctly replaced by sin (1) − 1. You can use any Sage
or Python commands inside a sageblock; all the commands get sent directly to
Sage.
sagesilent
This environment is like sageblock, but it does not typeset any of the code;
it just writes it to the .sage file. This is useful if you have to do some setup in
Sage that is not interesting or relevant to the document you are writing.
sageverbatim
This environment is the opposite of the one above: whatever you type will
be typeset, but not written into the .sage file. This allows you to typeset psuedocode, code that will fail, or take too much time to execute, or whatever.
comment
Logically, we now need an environment that neither typesets nor executes
your Sage code. . . but the verbatim package, which is always loaded when using
SageTEX, provides such an environment: comment. Another way to do this is to
put stuff between \iffalse and \fi.
sageexample
This environment allow you to include doctest-like snippets in your document
that will be nicely typeset. For example,
\begin{sageexample}
sage: 1+1
2
sage: factor(x^2 + 2*x + 1)
(x + 1)^2
\end{sageexample}
in your document will be typeset with the Sage inputs in the usual fixed-width font,
and the outputs will be typeset as if given to a \sage macro. When typesetting
the document, there is no test of the validity of the outputs (that is, typesetting with a typical LATEX-Sage-LATEX cycle does not do doctesting), but when using the sageexample environment, an extra file named “myfile_doctest.sage”
11
is created with the contents of all those environments; it is formatted so that
Sage can doctest that file. You should be able to doctest your document with
“sage -t myfile_doctest.sage”. (This does not always work; if this fails for
you, please contact the sage-support group.)
If you would like to see both the original text input and the typeset output,
you can issue \renewcommand{\sageexampleincludetextoutput}{True} in your
document. You can do the same thing with “False” to later turn it off. In the
above example, this would cause SageTEX to output both (x + 1)^2 and (x + 1)2
in your typeset document.
Just as in doctests, multiline statements are acceptable. The only limitation
is that triple-quoted strings delimited by """ cannot be used in a sageexample
environment; instead, you can use triple-quoted strings delimited by ’’’.
The initial implementation of this environment is due to Nicolas M. Thiéry.
sagecommandline
This environment is similar to the sageexample environment in that it allow
you to include doctest-like snippets in your document. The difference is that the
output is typeset as text, much like running Sage on the command line, using the
lstlisting environment. In particular, this environment provides Python syntax
highlighting and line numbers. For example,
\begin{sagecommandline}
sage: 1+1
2
sage: factor(x^2 + 2*x + 1)
(x + 1)^2
\end{sagecommandline}
\sagetexindent
There is one final bit to our verbatim-like environments: the indentation. The
SageTEX package defines a length \sagetexindent, which controls how much the
Sage code is indented when typeset. You can change this length however you like
with \setlength: do \setlength{\sagetexindent}{6ex} or whatever.
3.4
\sagetexpause
\sagetexunpause
Pausing SageTEX
Sometimes when you are writing a document, you may wish to temporarily turn
off or pause SageTEX to concentrate more on your document than on the Sage
computations, or to simply have your document typeset faster. You can do this
with the following commands.
Use these macros to “pause” and “unpause” SageTEX. After issuing this macro,
SageTEX will simply skip over the corresponding calculations. Anywhere a \sage
macro is used while paused, you will simply see “(SageTEX is paused)”, and anywhere a \sageplot macro is used, you will see:
12
SageTEX is paused; no graphic
Anything in the verbatim-like environments of section 3.3 will be typeset or not
as usual, but none of the Sage code will be executed.
Obviously, you use \sagetexunpause to unpause SageTEX and return to the
usual state of affairs. Both commands are idempotent; issuing them twice or more
in a row is the same as issuing them once. This means you don’t need to precisely
match pause and unpause commands: once paused, SageTEX stays paused until it
sees \sagetexunpause and vice versa.
4
Other notes
Here are some other notes on using SageTEX.
4.1
Using Beamer
The beamer package does not play nicely with verbatim-like environments unless
you ask it to. To use code block environments in a beamer presentation, do:
\begin{frame}[fragile]
\begin{sageblock}
# sage stuff
# more stuff \end{sageblock}
\end{frame}
For some reason, beamer inserts an extra line break at the end of the environment;
if you put the \end{sageblock} on the same line as the last line of your code,
it works properly. See section 12.9, “Verbatim and Fragile Text”, in the beamer
manual. (Thanks to Franco Saliola for reporting this.)
beamer’s overlays and \sageplot also need some help in order to work together, as discussed in this sage-support thread. If you want a plot to only appear
in a certain overlay, you might try something like this in your frame:
\begin{itemize}
\item item 1
\item item 2
\item \sageplot[height=4cm][png]{(plot_slope_field(2*x,(x,-4,4),
(y,-4,4))+(x^2-2).plot(-2,2))}
\end{itemize}
but the plot will appear on all the overlays, instead of the third. The solution is
to use the \visible macro:
13
\begin{itemize}
\item item 1
\item item 2
\item \visible<3->{\sageplot[height=4cm][png]{(plot_slope_field(2*x,(x,-4,4),
(y,-4,4))+(x^2-2).plot(-2,2))}}
\end{itemize}
Then the plot will only appear on the third (and later) overlays. (Thanks to
Robert Mařı́k for this solution.)
4.2
Using the rccol package
If you are trying to use the \sage macro inside a table when using the rccol
package, you need to use an extra pair of braces or typesetting will fail. That is,
you need to do something like this:
abc & {\sage{foo.n()}} & {\sage{bar}} \\
with each “\sage{}” enclosed in an extra {}. Thanks to Sette Diop for reporting
this.
4.3
Plotting from Mathematica, Maple, etc.
Sage can use Mathematica, Maple, and friends and can tell them to do plotting,
but since it cannot get those plots into a Sage graphics object, you cannot use
\sageplot to use such graphics. You’ll need to use the method described in
“But that’s not good enough!” (section 3.2.2) with some additional bits to get
the directory right—otherwise your file will get saved to someplace in a hidden
directory.
For Mathematica, you can do something like this inside a sagesilent or
sageblock environment:
mathematica(’myplot = commands to make your plot’)
mathematica(’Export["%s/graphicsfile.eps", myplot]’ % os.getcwd())
then put \includegraphics[opts]{graphicsfile} in your file.
For Maple, you’ll need something like
maple(’plotsetup(ps, plotoutput=‘%s/graphicsfile.eps‘, \
plotoptions=‘whatever‘);’ % os.getcwd())
maple(’plot(function, x=1..whatever);’)
and then \includegraphics as necessary.
These interfaces, especially when plotting, can be finicky. The above commands are just meant to be a starting point.
14
4.4
Sending SageTEX files to others who don’t use Sage
What can you do when sending a LATEX document that uses SageTEX to a colleague
who doesn’t use Sage?4 The best option is to bring your colleague into the light
and get him or her using Sage! But this may not be feasible, because some
(most?) mathematicians are fiercely crotchety about their choice of computer
algebra system, or you may be sending a paper to a journal or the arXiv, and such
places will not run Sage just so they can typeset your paper—at least not until
Sage is much closer to its goal of world domination.
How can you send your SageTEX-enabled document to someone else who doesn’t
use Sage? The easiest way is to simply include with your document the following
files:
1. sagetex.sty
2. the generated .sout and .scmd files
3. the sage-plots-for-hfilename i.tex directory and its contents
As long as sagetex.sty is available, your document can be typeset using any
reasonable LATEX system. Since it is very common to include graphics files with
a paper submission, this is a solution that should always work. (In particular, it
will work with arXiv submissions.)
There is another option, and that is to use the makestatic.py script included
with SageTEX.
Use of the script is quite simple. Copy it and sagetexparse.py to the directory
with your document, and run
python makestatic.py inputfile [outputfile]
where inputfile is your document. (You can also set the executable bit of
makestatic.py and use ./makestatic.py.) This script needs the pyparsing module to be installed.5 You may optionally specify outputfile; if you do so, the
results will be written to that file. If the file exists, it won’t be overwritten unless
you also specify the -o switch.
You will need to run this after you’ve compiled your document and run Sage
on the .sage file. The script reads in the .sout file and replaces all the calls to
\sage and \sageplot with their plain LATEX equivalent, and turns the sageblock
and sageverbatim environments into verbatim environments. Any sagesilent
environment is turned into a comment environment. Any sagecommandline environment is turned into a lstlisting environment, typesetting the relevant part
of the .scmd file. The resulting document should compile to something identical,
or very nearly so, to the original file.
One large limitation of this script is that it can’t change anything while SageTEX
is paused, since Sage doesn’t compute anything for such parts of your document.
4 Or
who cannot use Sage, since currently SageTEX is not very useful on Windows.
you don’t have pyparsing installed,
you can simply copy the
$SAGE ROOT/local/lib/python/matplotlib/pyparsing.py into your directory.
5 If
15
file
It also doesn’t check to see if pause and unpause commands are inside comments
or verbatim environments. If you’re going to use makestatic.py, just remove all
pause/unpause statements.
The parsing that makestatic.py does is pretty good, but not perfect. Right
now it doesn’t support having a comma-separated list of packages, so you can’t
have \usepackage{sagetex, foo}. You need to have just \usepackage{sagetex}.
(Along with package options; those are handled correctly.) If you find other parsing errors, please let me know.
4.5
Extracting the Sage code from a document
This next script is probably not so useful, but having done the above, this was
pretty easy. The extractsagecode.py script does the opposite of makestatic.py,
in some sense: given a document, it extracts all the Sage code and removes all the
LATEX.
Its usage is the same as makestatic.py.
Note that the resulting file will almost certainly not be a runnable Sage script,
since there might be LATEX commands in it, the indentation may not be correct,
and the plot options just get written verbatim to the file. Nevertheless, it might
be useful if you just want to look at the Sage code in a file.
5
Using SageTEX without Sage installed
You may want to edit and typeset a SageTEX-ified file on a computer that doesn’t
have Sage installed. How can you do that? We need to somehow run Sage on
the .sage file. The included script remote-sagetex.py takes advantage of Sage’s
network transparency and will use a remote server to do all the computations.
Anywhere in this manual where you are told to “run Sage”, instead of actually
running Sage, you can run
python remote-sagetex.py filename.sage
The script will ask you for a server, username, and password, then process all
your code and write a .sout file and graphics files exactly as if you had used a
local copy of Sage to process the .sage script. (With some minor limitations and
differences; see below.)
One important point: the script requires Python 2.6. It will not work with
earlier versions. (It will work with Python 3.0 or later with some trivial changes.)
You can provide the server, username and password with the command-line
switches --server, --username, and --password, or you can put that information
into a file and use the --file switch to specify that file. The format of the file
must be like the following:
# hash mark at beginning of line marks a comment
server = "http://example.com:1234"
username = ’my_user_name’
password = ’s33krit’
16
As you can see, it’s really just like assigning a string to a variable in Python. You
can use single or double quotes and use hash marks to start comments. You can’t
have comments on the same line as an assignment, though. You can omit any of
those pieces of information information; the script will ask for anything it needs
to know. Information provided as a command line switch takes precedence over
anything found in the file.
You can keep this file separate from your LATEX documents in a secure location;
for example, on a USB thumb drive or in an automatically encrypted directory
(like ~/Private in Ubuntu). This makes it much harder to accidentally upload
your private login information to the arXiv, put it on a website, send it to a
colleague, or otherwise make your private information public.
5.1
Limitations of remote-sagetex.py
The remote-sagetex.py script has several limitations. It completely ignores the
epstopdf and imagemagick flags. The epstopdf flag is not a big deal, since it
was originally introduced to work around a matplotlib bug which has since been
fixed. Not having imagemagick support means that you cannot automatically
convert 3D graphics to eps format; using pdflatex to make PDFs works around
this issue.
5.2
Other caveats
Right now, the “simple server API” that remote-sagetex.py uses is not terribly
robust, and if you interrupt the script, it’s possible to leave an idle session running
on the server. If many idle sessions accumulate on the server, it can use up a lot of
memory and cause the server to be slow, unresponsive, or maybe even crash. For
now, I recommend that you only run the script manually. It’s probably best to not
configure your TEX editing environment to automatically run remote-sagetex.py
whenever you typeset your document, at least not without showing you the output
or alerting you about errors.
6
Implementation
There are two pieces to this package: a LATEX style file, and a Python module.
They are mutually interdependent, so it makes sense to document them both here.
6.1
The style file
All macros and counters intended for use internal to this package begin with “ST@”.
6.1.1
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
17
1
2
\RequirePackage{verbatim}
\RequirePackage{fancyvrb}
and listings for the sagecommandline environment.
\RequirePackage{listings}
\RequirePackage{color}
5 \lstdefinelanguage{Sage}[]{Python}
6
{morekeywords={False,sage,True},sensitive=true}
7 \lstdefinelanguage{SageOutput}[]{}
8
{morekeywords={False,True},sensitive=true}
9 \lstdefinestyle{DefaultSageInputOutput}{
10
nolol,
11
identifierstyle=,
12
name=sagecommandline,
13
xleftmargin=5pt,
14
numbersep=5pt,
15
aboveskip=0pt,
16
belowskip=0pt,
17
breaklines=true,
18
numberstyle=\footnotesize,
19
numbers=right
20 }
21 \lstdefinestyle{DefaultSageInput}{
22
language=Sage,
23
style=DefaultSageInputOutput,
24
basicstyle={\ttfamily\bfseries},
25
commentstyle={\ttfamily\color{dgreencolor}},
26
keywordstyle={\ttfamily\color{dbluecolor}\bfseries},
27
stringstyle={\ttfamily\color{dgraycolor}\bfseries},
28 }
29 \lstdefinestyle{DefaultSageOutput}{
30
language=SageOutput,
31
style=DefaultSageInputOutput,
32
basicstyle={\ttfamily},
33
commentstyle={\ttfamily\color{dgreencolor}},
34
keywordstyle={\ttfamily\color{dbluecolor}},
35
stringstyle={\ttfamily\color{dgraycolor}},
36 }
37 \lstdefinestyle{SageInput}{
38
style=DefaultSageInput,
39 }
40 \lstdefinestyle{SageOutput}{
41
style=DefaultSageOutput,
42 }
43 \definecolor{dbluecolor}{rgb}{0.01,0.02,0.7}
44 \definecolor{dgreencolor}{rgb}{0.2,0.4,0.0}
45 \definecolor{dgraycolor}{rgb}{0.30,0.3,0.30}
3
4
Unsurprisingly, the \sageplot command works poorly without graphics support.
46
\RequirePackage{graphicx}
18
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.
\RequirePackage{makecmds}
\RequirePackage{ifpdf}
49 \RequirePackage{ifthen}
47
48
Next set up the counters, default indent, and flags.
\newcounter{ST@inline}
51 \newcounter{ST@plot}
52 \setcounter{ST@inline}{0}
53 \setcounter{ST@plot}{0}
54 \newlength{\sagetexindent}
55 \setlength{\sagetexindent}{5ex}
56 \newif\ifST@paused
57 \ST@pausedfalse
50
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:
\AtBeginDocument{\@ifundefined{ST@final}{%
\newwrite\ST@sf%
60 \immediate\openout\ST@sf=\jobname.sage%
58
59
\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 31 for discussion of file extensions. The “(\jobname.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.
\newcommand{\ST@wsf}[1]{\immediate\write\ST@sf{#1}}%
\ST@wsf{%
63 # This file (\jobname.sage) was *autogenerated* from \jobname.tex with
64 sagetex.sty version \ST@ver.^^J%
65 import sagetex^^J%
66 _st_ = sagetex.SageTeXProcessor(’\jobname’)^^J%
67 _do_ver_check_ = \ST@versioncheck^^J%
68 if _do_ver_check_ and sagetex.__version__.find(’\ST@ver’) == -1:^^J
69
import sys^^J
70
print ’{0}.sage was generated with sagetex.sty version \ST@ver,’.format(
71
sys.argv[0].split(’.’)[0])^^J
72
print ’but is being processed by sagetex.py version {0}.’.format(
73
’ ’.join(sagetex.__version__.strip().strip(’[’).split()[0:2]))^^J
61
62
19
74
75
print ’SageTeX version mismatch! Exiting.’^^J
sys.exit(int(1))}}%
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.
76
\ST@dodfsetup
{\newcommand{\ST@wsf}[1]{\relax}}}
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.
\newcommand{\ST@dodfsetup}{%
\@ifundefined{ST@diddfsetup}{%
79 \newwrite\ST@df%
80 \immediate\openout\ST@df=\jobname_doctest.sage%
81 \immediate\write\ST@df{r"""^^J%
82 This file was *autogenerated* from \jobname.tex with sagetex.sty^^J%
83 version \ST@ver. It contains the contents of all the^^J%
84 sageexample environments from \jobname.tex. You should be able to^^J%
85 doctest this file with "sage -t \jobname_doctest.sage".^^J%
86 ^^J%
87 It is always safe to delete this file; it is not used in typesetting your^^J%
88 document.^^J}%
89 \AtEndDocument{\immediate\write\ST@df{"""}}%
90 \gdef\ST@diddfsetup{x}}%
91 {\relax}}
77
78
\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.
92
\newcommand{\ST@wdf}[1]{\immediate\write\ST@df{#1}}
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.
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 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.
\DeclareOption{final}{%
\newcommand{\ST@final}{x}%
95
\IfFileExists{\jobname.sout}{}{\AtEndDocument{\PackageWarningNoLine{sagetex}%
96
{‘final’ option provided, but \jobname.sout^^Jdoesn’t exist! No Sage
97
input will appear in your document. Remove the ‘final’^^Joption and
98
rerun LaTeX on your document}}}}
93
94
For imagemagick, we set two flags: one for LATEX and one for Sage. It’s important
that we set ST@useimagmagick before the beginning of the document, so that
20
the graphics commands can check that. We do wait until the beginning of the
document to do file writing stuff.
\DeclareOption{imagemagick}{%
\newcommand{\ST@useimagemagick}{x}%
101
\AtBeginDocument{%
102
\@ifundefined{ST@final}{%
103
\ST@wsf{_st_.useimagemagick = True}}{}}}
99
100
For epstopdf, we just set a flag for Sage.
\DeclareOption{epstopdf}{%
\AtBeginDocument{%
106 \@ifundefined{ST@final}{%
107
\ST@wsf{_st_.useepstopdf = True}}{}}}
104
105
By default, we check to see if the .py and .sty file versions match. But we let the
user disable this.
\newcommand{\ST@versioncheck}{True}
\DeclareOption{noversioncheck}{%
110
\renewcommand{\ST@versioncheck}{False}}
111 \ProcessOptions\relax
108
109
The \relax is a little incantation suggested by the “LATEX 2ε 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 (Latexmk, for example) that read
the .log file or terminal output to detect the need for another typesetting run to
do so. If the “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.
112
\InputIfFileExists{\jobname.sout}{}{\typeout{No file \jobname.sout.}}
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.
113
\AtBeginDocument{\provideenvironment{NoHyper}{}{}}
6.1.2
\ST@sage
The \sage and \sagestr macros
This macro combines \ref, \label, and Sage all at once. First, we use Sage to
get a LATEX 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
21
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.
\newcommand{\ST@sage}[1]{\ST@wsf{%
try:^^J
116 _st_.inline(\theST@inline, #1)^^J%
117 except:^^J
118 _st_.goboom(\the\inputlineno)}%
114
115
The inline function of the Python module is documented on page 32. Back in
LATEX-land: if paused, say so.
119
120
\ifST@paused
\mbox{(Sage\TeX{} is paused)}%
Otherwise. . . 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 \newlabels and gets deeply confused.
Fortunately the hyperref folks are willing to accomodate people like us, and give
us a NoHyper environment.
121
122
\else
\begin{NoHyper}\ref{@sageinline\theST@inline}\end{NoHyper}
Now check if the label has already been defined. (The internal implementation of
labels in LATEX 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.
123
124
\@ifundefined{r@@sageinline\theST@inline}{\gdef\ST@rerun{x}}{}
\fi
In any case, the last thing to do is step the counter.
125
\sage
This is the user-visible macro; it runs Sage’s latex() on its argument.
126
\sagestr
\newcommand{\sage}[1]{\ST@sage{latex(#1)}}
ilike above, but doesn’t run latex
127
\percent
\stepcounter{ST@inline}}
\newcommand{\sagestr}[1]{\ST@sage{#1}}
A macro that inserts a percent sign. This is more-or-less stolen from the Docstrip
manual; there they change the catcode inside a group and use gdef, but here we
try to be more LATEXy and use \newcommand.
\catcode‘\%=12
\newcommand{\percent}{%}
130 \catcode‘\%=14
128
129
6.1.3
The \sageplot macro and friends
Plotting is rather more complicated, and requires several helper macros that accompany \sageplot.
22
\ST@plotdir
A little abbreviation for the plot directory. We don’t use \graphicspath because
it’s apparently slow—also, since we know right where our plots are going, no need
to have LATEX looking for them.
131
\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.
132
\sageplot
\newcommand{\ST@plotdir}{sage-plots-for-\jobname.tex}
\newcommand{\ST@missingfilebox}{\framebox[2cm]{\rule[-1cm]{0cm}{2cm}\textbf{??}}}
This function is similar to \sage. The neat thing that we take advantage of is that
commas aren’t special for arguments to LATEX 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 LATEX’s
\newcommand; we use Scott Pakin’s brilliant newcommand package to create this
macro; the options I fed to his script were similar to this:
MACRO sageplot OPT[#1={width}] OPT[#2={notprovided}] #3
Observe that we are using a Python script to write LATEX code which writes Python
code which writes LATEX code. Crazy!
Here’s the wrapper command which does whatever magic we need to get two
optional arguments.
133
134
\newcommand{\sageplot}[1][width=.75\textwidth]{%
\@ifnextchar[{\ST@sageplot[#1]}{\ST@sageplot[#1][notprovided]}}
The first optional argument #1 will get shoved right into the optional argument
for \includegraphics, so the user has easy control over the LATEX aspects of the
plotting. We define a default size of 3/4 the textwidth, which seems reasonable.
(Perhaps a future version of SageTEX 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.
\ST@sageplot
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.
\def\ST@sageplot[#1][#2]#3{\ST@wsf{try:^^J
_st_.plot(\theST@plot, format=’#2’, _p_=#3)^^Jexcept:^^J
137 _st_.goboom(\the\inputlineno)}%
135
136
The Python plot function is documented on page 35.
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. Figure 1 shows what we do; for
completeness—and because I think drawing trees with Tik Z is really cool—we
show what \ST@inclgrfx does in Figure 2. This entire complicated business is
23
DVI or PDF?
DVI
PDF
Format provided?
yes
no
STig EPS
Format provided?
no
STig PDF
IM option set?
no
Warn that DVI +
PNG = bad
yes
STig #2
yes
STig EPS
Figure 1: The logic tree that \sageplot uses to decide whether to run
\includegraphics or to yell at the user. “Format” is the #2 argument to
\sageplot, “STig ext” means a call to \ST@inclgrfx with “ext” as the second
argument, and “IM” is Imagemagick.
intended to avoid doing an \includegraphics command on a file that doesn’t
exist, and to issue warnings appropriate to the situation.
If we are creating a PDF, we check to see if the user asked for a different
format, and use that if necessary:
\ifpdf
\ifthenelse{\equal{#2}{notprovided}}%
140
{\ST@inclgrfx{#1}{pdf}}%
141
{\ST@inclgrfx{#1}{#2}}%
138
139
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.)
\else
\ifthenelse{\equal{#2}{notprovided}}%
144
{\ST@inclgrfx{#1}{eps}}%
142
143
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.
145
146
147
148
149
150
151
152
153
{\@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
24
\ST@plotdir/plot-\theST@plot.#2\space on page \thepage\space
does not exist. Plot command is}%
\gdef\ST@rerun{x}}}%
154
155
156
Otherwise, we are using Imagemagick, so try to include an EPS file anyway.
{\ST@inclgrfx{#1}{eps}}}%
157
158
\fi
Step the counter and we’re done with the usual work.
159
\ST@inclgrfx
\stepcounter{ST@plot}}
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.
\newcommand{\ST@inclgrfx}[2]{\ifST@paused
\fbox{\rule[-1cm]{0cm}{2cm}Sage\TeX{} is paused; no graphic}
162 \else
163
\IfFileExists{\ST@plotdir/plot-\theST@plot.#2}%
164
{\includegraphics[#1]{\ST@plotdir/plot-\theST@plot.#2}}%
160
161
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.
{\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}}}
165
166
167
168
169
170
171
172
173
174
175
176
177
178
\fi}
Figure 2 makes this a bit clearer.
6.1.4
\ST@beginsfbl
Verbatim-like environments
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
25
Paused?
yes
no
Insert “we’re paused” box
Does EXT file exist?
yes
no
Does a PNG file exist?
no
yes
Warn user to
rerun Sage
Making a PDF?
yes
includegraphics PNG
Use includegraphics
no
Warning:
DVI, latex
incompatible
Figure 2: The logic used by the \ST@inclgrfx command.
complaining about indentation, but lets us tell the user where things went wrong.
The blockbegin and blockend functions are documented on page 32. The last
bit is some magic from the verbatim package manual that makes LATEX respect
line breaks.
\newcommand{\ST@beginsfbl}{%
\@bsphack\ST@wsf{%
181 _st_.blockbegin()^^Jtry:}%
182
\let\do\@makeother\dospecials\catcode‘\^^M\active}
179
180
\ST@endsfbl
The companion to \ST@beginsfbl.
\newcommand{\ST@endsfbl}{%
\ST@wsf{except:^^J
185 _st_.goboom(\the\inputlineno)^^J_st_.blockend()}}
183
184
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.
sageblock
This environment does both: it typesets your code and puts it into the .sage file
for execution by Sage.
186
\newenvironment{sageblock}{\ST@beginsfbl%
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.
187
\def\verbatim@processline{\ST@wsf{ \the\verbatim@line}%
26
Next, we typeset your code and start the verbatim environment.
188
189
\hspace{\sagetexindent}\the\verbatim@line\par}%
\verbatim}%
At the end of the environment, we put a chunk into the .sage file and stop the
verbatim environment.
190
sagesilent
{\ST@endsfbl\endverbatim}
This is from the verbatim package manual. It’s just like the above, except we
don’t typeset anything.
\newenvironment{sagesilent}{\ST@beginsfbl%
\def\verbatim@processline{\ST@wsf{ \the\verbatim@line}}%
193 \verbatim@start}%
194 {\ST@endsfbl\@esphack}
191
192
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.
\newenvironment{sageverbatim}{%
\def\verbatim@processline{\hspace{\sagetexindent}\the\verbatim@line\par}%
197 \verbatim}%
198 {\endverbatim}
195
196
Logically, we now need an environment which neither typesets nor writes code
to the .sage file. The verbatim package’s comment environment does that.
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 due to Nicolas M. Thiéry.
\newcommand{\sageexampleincludetextoutput}{False}
\newenvironment{sageexample}{%
201
\ST@wsf{%
202 try:^^J
203 _st_.doctest(\theST@inline, r"""}%
204
\ST@dodfsetup%
205
\ST@wdf{Sage example, line \the\inputlineno::^^J}%
206
\begingroup%
207
\@bsphack%
208
\let\do\@makeother\dospecials%
209
\catcode‘\^^M\active%
210
\def\verbatim@processline{%
211
\ST@wsf{\the\verbatim@line}%
212
\ST@wdf{\the\verbatim@line}%
213
}%
214
\verbatim@start%
215 }
216 {
199
200
27
217
218
219
220
221
222
223
224
225
226
227
228
229
sagecommandline
\@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}}
This environment is similar to the sageexample environment, but typesets the
sage output as text with python syntax highlighting.
\newcommand{\sagecommandlinetextoutput}{True}
\newlength{\sagecommandlineskip}
232 \setlength{\sagecommandlineskip}{8pt}
233 \newenvironment{sagecommandline}{%
234
\ST@wsf{%
235 try:^^J
236 _st_.commandline(\theST@inline, r"""}%
237
\ST@dodfsetup%
238
\ST@wdf{Sage commandline, line \the\inputlineno::^^J}%
239
\begingroup%
240
\@bsphack%
241
\let\do\@makeother\dospecials%
242
\catcode‘\^^M\active%
243
\def\verbatim@processline{%
244
\ST@wsf{\the\verbatim@line}%
245
\ST@wdf{\the\verbatim@line}%
246
}%
247
\verbatim@start%
248 }
249 {
250
\@esphack%
251
\endgroup%
252
\ST@wsf{%
253
""", globals(), locals(), \sagecommandlinetextoutput)^^Jexcept:^^J
254
_st_.goboom(\the\inputlineno)}%
255
\ifST@paused%
256
\mbox{(Sage\TeX{} is paused)}%
257
\else%
258
\begin{NoHyper}\ref{@sageinline\theST@inline}\end{NoHyper}%
259
\@ifundefined{r@@sageinline\theST@inline}{\gdef\ST@rerun{x}}{}%
260
\fi%
261
\ST@wdf{}%
262
\stepcounter{ST@inline}}
230
231
28
6.1.5
Pausing SageTEX
How can one have Sage to stop processing SageTEX 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 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 SageTEX, 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 SageTEX: it mixes LATEX and
Sage/Python, and often what is difficult or impossible in one system is trivial
in the other.
sagetexpause
This macro pauses SageTEX by effectively commenting out code in the .sage file.
When running the corresponding .sage file, Sage will skip over any commands
issued while SageTEX is paused.
\newcommand{\sagetexpause}{\ifST@paused\relax\else
\ST@wsf{print ’SageTeX paused on \jobname.tex line \the\inputlineno’^^J"""}
265 \ST@pausedtrue
266 \fi}
263
264
sagetexunpause
This is the obvious companion to \sagetexpause.
\newcommand{\sagetexunpause}{\ifST@paused
\ST@wsf{"""^^Jprint ’SageTeX unpaused on \jobname.tex line \the\inputlineno’}
269 \ST@pausedfalse
270 \fi}
267
268
6.1.6
End-of-document 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.
\AtEndDocument{\ifST@paused
\ST@wsf{"""^^Jprint ’SageTeX unpaused at end of \jobname.tex’}
273 \fi
274 \ST@wsf{_st_.endofdoc()}%
275 \@ifundefined{ST@rerun}{}%
271
272
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, LATEX will
complain about undefined references if you haven’t run the Sage script—and for
many LATEX users, myself included, the warning “there were undefined references”
is a signal to run LATEX again. But to fix these particular undefined references,
you need to run Sage. We also suppressed file-not-found errors for graphics files,
and need to tell the user what to do about that.
29
At any rate, we tell the user to run Sage if it’s necessary.
{\PackageWarningNoLine{sagetex}{There were undefined Sage formulas
and/or plots.^^JRun Sage on \jobname.sage, and then run
278 LaTeX on \jobname.tex again}}}
276
277
6.2
The Python module
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.
A note on Python and Docstrip There is one tiny potential source of confusion when documenting Python code with 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 will not be written to the output file. This is only a problem if you begin
the line with a (single) percent sign; there are no troubles otherwise.
On to the code: the sagetex.py file is intended to be used as a module and
doesn’t do anything useful when called directly, so if someone does that, warn
them. We do this right away so that we print this and exit before trying to import
any Sage modules; that way, this error message gets printed whether you run the
script with Sage or with Python. Since SageTEX is now distributed with Sage and
sagetex.py now lives almost exclusively deep within the Sage ecosystem, this
check is not so necessary and will be removed sometime soon.
import sys
if __name__ == "__main__":
281
print("""This file is part of the SageTeX package.
282 It is not meant to be called directly.
279
280
283
This file will be automatically used by Sage scripts generated from a
LaTeX document using the SageTeX package.""")
286
sys.exit()
284
285
Munge the version string (which we get from sagetexpackage.dtx) to extract
what we want, then import what we need:
version = ’ ’.join(__version__.strip(’[’).split()[0:2])
from sage.misc.latex import latex
289 from sage.misc.preparser import preparse
290 import os
291 import os.path
292 import hashlib
293 import traceback
294 import subprocess
295 import shutil
287
288
We define a 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
30
nicer and easier.
class SageTeXProcessor():
def __init__(self, jobname):
298
self.progress(’Processing Sage code for {0}.tex...’.format(jobname))
299
self.didinitplot = False
300
self.useimagemagick = False
301
self.useepstopdf = False
302
self.plotdir = ’sage-plots-for-’ + jobname + ’.tex’
303
self.filename = jobname
304
self.name = os.path.splitext(jobname)[0]
296
297
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.
305
306
307
308
self.souttmp = open(self.filename + ’.sout.tmp’, ’w’)
self.souttmp.write(
"% This file was *autogenerated* from {0}.sage with\n".format(self.name) +
"% sagetex.py version {0}\n".format(version) )
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 file and
position in the data members
309
310
311
312
313
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.
314
315
316
317
318
319
initplot
self.scmdtmp = open(self.filename + ’.scmd.tmp’, ’w’)
self.scmdtmp.write(
"% This file was *autogenerated* from {0}.sage with\n".format(self.name) +
"% sagetex.py version {0}\n".format(version) )
self.scmdpos = 3
def progress(self, t,linebreak=True):
if linebreak:
print(t)
else:
sys.stdout.write(t)
sys.stdout.flush()
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 LATEX file being processed so that if there are
multiple .tex files in a directory, we don’t overwrite plots from another file.
320
321
def initplot(self):
self.progress(’Initializing plots directory’)
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
31
comp.text.tex, the best solution I found for finding the file extension is to look
through the .log file.
322
323
324
325
inline
if os.path.isdir(self.plotdir):
shutil.rmtree(self.plotdir)
os.mkdir(self.plotdir)
self.didinitplot = True
This function works with \sage from the style file (see section 6.1.2) to put Sage
output into your LATEX file. Usually, when you use \label, it writes a line such
as
\newlabel{labelname}{{section number}{page number}}
to the .aux file. When you use the hyperref package, there are more fields in the
second argument, but the first two 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.
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 very short function:
326
327
328
329
def inline(self, counter, s):
self.progress(’Inline formula {0}’.format(counter))
self.souttmp.write(’\\newlabel{@sageinline’ + str(counter) + ’}{{%\n’ +
s.rstrip() + ’}{}{}{}{}}\n’)
We are using five fields, just like hyperref does, because that works whether
or not hyperref is loaded. Using two fields, as in plain LATEX, doesn’t work if
hyperref is loaded.
savecmd
Analogous to inline, this method saves the input string s to the temporary .scmd
file. As an added bonus, it returns a pair of line numbers in the .scmd file, the
first and last line of the newly-added output.
330
331
332
333
334
335
336
blockbegin
blockend
def savecmd(self, counter, s):
self.progress(’Sage commandline {0}’.format(counter))
self.scmdtmp.write(s.rstrip() + "\n")
begin = self.scmdpos
end = begin + len(s.splitlines()) - 1
self.scmdpos = end+1
return begin, end
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 section 6.1.4 use these functions.
337
338
339
340
def blockbegin(self):
self.progress(’Code block begin...’, False)
def blockend(self):
self.progress(’end’)
32
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.
341
342
343
344
345
346
def doctest(self, counter, str, globals, locals, include_text_output):
print ’in doctest’
current_statement = None
current_lines = None
latex_string = ""
line_iterator = (line.lstrip() for line in str.splitlines())
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# Gobbles everything until the first "sage: ..." block
for line in line_iterator:
if line.startswith("sage: "):
break
else:
return
sage_block = 0
while True:
# At each
assert line.startswith("sage: ")
current_statement = line[6:]
current_lines = " "+line
for line in line_iterator:
if line.startswith("sage: "):
break
elif line.startswith("..."):
current_statement +="\n"+line[6:]
current_lines +="\n "+line
elif include_text_output:
current_lines +="\n "+line
else:
line = None # we reached the last line
# Now we have digested everything from the current sage: ... to the next one or to the
# Let us handle it
verbatimboxname = "@sageinline%s-code%s"%(counter,sage_block)
self.souttmp.write("\\begin{SaveVerbatim}{%s}\n"%verbatimboxname)
self.souttmp.write(current_lines)
self.souttmp.write("\n\\end{SaveVerbatim}\n")
latex_string += "\UseVerbatim{%s}\n"%verbatimboxname
current_statement = preparse(current_statement)
try: # How to test whether the code is an Python expression or a statement?
# In the first case, we compute the result and include it in the latex
result = eval(current_statement, globals, locals)
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 §11.5 of Herbert Voß’s “Math Mode”. Be careful when using TEX commands
and Python 3 (or 2.6+) curly brace string formatting; either double braces or
separate strings, as below.
33
latex_string += r"""\abovedisplayskip=0pt plus 3pt
\abovedisplayshortskip=0pt plus 3pt
383 \begin{displaymath}""" + "\n {0}\n".format(latex(result)) + r"\end{displaymath}" + "\n"
384
except SyntaxError:
385
# If this fails, we assume that the code was a statement, and just execute it
386
exec current_statement in globals, locals
387
current_lines = current_statement = None
388
if line is None: break
389
sage_block += 1
390
self.inline(counter, latex_string)
381
382
commandline
This function handles the commandline environment, which typesets Sage code
and its output.
391
392
393
394
395
396
397
def commandline(self, counter, str, globals, locals, text_output):
print ’in commandline’
current_statement = None
current_lines = None
line_iterator = (line.lstrip() for line in str.splitlines())
latex_string = r"\vspace{\sagecommandlineskip}" + "\n"
bottom_skip = ’’
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# Gobbles everything until the first "sage: ..." block
for line in line_iterator:
if line.startswith("sage: "):
break
else:
return
sage_block = 0
while True:
# At each
assert line.startswith("sage: ")
current_statement = line[6:]
current_lines = line
for line in line_iterator:
if line.startswith("sage: "):
break
elif line.startswith("...
"):
current_statement += "\n"+line[6:]
current_lines += "\n"+line
else:
line = None # we reached the last line
# Now have everything from "sage:" to the next "sage:"
420
421
422
423
424
if current_lines.find(’#@’)>=0:
escapeoption = ’,escapeinside={\\#@}{\\^^M}’
else:
escapeoption = ’’
425
426
begin, end = self.savecmd(counter,current_lines)
34
427
latex_string += r"\lstinputlisting[firstline={0},lastline={1},firstnumber={2},style=Sa
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
plot
current_statement = preparse(current_statement)
try: # is it an expression?
result = eval(current_statement, globals, locals)
resultstr = "{0}".format(result)
begin, end = self.savecmd(counter,resultstr)
if text_output:
latex_string += r"\lstinputlisting[firstline={0},lastline={1},firstnumber={2},st
bottom_skip = r"\vspace{\sagecommandlineskip}" + "\n"
else:
latex_string += (
r"\begin{displaymath}" + "\n" +
latex(result)
+ "\n" +
r"\end{displaymath}"
+ "\n" )
bottom_skip = ’’
except SyntaxError: # must be a statement!
exec current_statement in globals, locals
current_lines = current_statement = None
if line is None: break
sage_block += 1
latex_string += bottom_skip + r"\noindent" + "\n"
self.inline(counter, latex_string)
I hope it’s obvious that this function does plotting. It’s the Python counterpart
of \ST@sageplot described in section 6.1.3. As mentioned in the \sageplot code,
we’re taking advantage of two things: first, that LATEX 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.
450
451
452
453
def plot(self, counter, _p_, format=’notprovided’, **kwargs):
if not self.didinitplot:
self.initplot()
self.progress(’Plot {0}’.format(counter))
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.6
454
455
456
457
458
if format == ’notprovided’:
formats = [’eps’, ’pdf’]
else:
formats = [format]
for fmt in formats:
If we’re making a PDF and have been told to use epstopdf, do so, then skip the
rest of the loop.
6 Yes, there’s pdfsync, but full support for that is still rare in Linux, so producing EPS and
PDF is the best solution for now.
35
459
460
461
462
463
464
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
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.
465
466
467
468
469
470
471
472
473
474
475
476
plotfilename = os.path.join(self.plotdir, ’plot-{0}.{1}’.format(counter, fmt))
try:
_p_.save(filename=plotfilename, **kwargs)
except ValueError as inst:
if ’filetype not supported by save’ in 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
If the user provides a format and specifies the imagemagick option, we try to
convert the newly-created file into EPS format.
477
478
479
480
toeps
if format != ’notprovided’ and self.useimagemagick:
self.progress(’Calling Imagemagick to convert plot-{0}.{1} to EPS’.format(
counter, format))
self.toeps(counter, format)
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 SageTEX style file and is making a graphic file with a nondefault
extension.
481
482
483
484
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)])
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.
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 LATEX file things went bad,
36
so we do that, give them the traceback, and exit after removing the .sout.tmp
and .scmd.tmp file.
485
486
487
488
489
490
491
492
493
494
495
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()
self.scmdtmp.close()
os.remove(self.filename + ’.sout.tmp’)
os.remove(self.filename + ’.scmd.tmp’)
sys.exit(int(1))
We use int(1) above to make sure sys.exit sees a Python integer; see ticket
#2861.
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 LATEX 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-SageTEX 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 SageTEX 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 part of the .sage file. By design,
the source file line numbers only appear in calls to goboom and pause/unpause
lines, so we will strip those lines out. What we do below is exactly equivalent to
running
egrep -v ’^( _st_.goboom|print .SageT)’ filename.sage | md5sum
in a shell.
496
497
498
499
500
501
502
503
504
505
def endofdoc(self):
sagef = open(self.filename + ’.sage’, ’r’)
m = hashlib.md5()
for line in sagef:
if line[0:12] != " _st_.goboom" and line[0:12] != "print ’SageT":
m.update(line)
s = ’%’ + m.hexdigest() + ’% md5sum of corresponding .sage file\
(minus "goboom" and pause/unpause lines)\n’
self.souttmp.write(s)
self.scmdtmp.write(s)
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 \refs find a
37
\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.
self.souttmp.close()
self.scmdtmp.close()
os.rename(self.filename + ’.sout.tmp’, self.filename + ’.sout’)
os.rename(self.filename + ’.scmd.tmp’, self.filename + ’.scmd’)
self.progress(’Sage processing complete. Run LaTeX on {0}.tex again.’.format(
self.filename))
506
507
508
509
510
511
7
Included Python scripts
Here we describe the Python code for makestatic.py, which removes SageTEX
commands to produce a “static” file, and extractsagecode.py, which extracts
all the Sage code from a .tex file.
7.1
makestatic.py
First, makestatic.py script. It’s about the most basic, generic Python script
taking command-line arguments that you’ll find. The #!/usr/bin/env python
line is provided for us by the .ins file’s preamble, so we don’t put it here.
import sys
import time
514 import getopt
515 import os.path
516 from sagetexparse import DeSageTex
512
513
517
518
519
def usage():
print("""Usage: %s [-h|--help] [-o|--overwrite] inputfile [outputfile]
520
Removes SageTeX macros from ‘inputfile’ and replaces them with the
Sage-computed results to make a "static" file. You’ll need to have run
523 Sage on ‘inputfile’ already.
521
522
524
‘inputfile’ can include the .tex extension or not. If you provide
‘outputfile’, the results will be written to a file of that name.
527 Specify ‘-o’ or ‘--overwrite’ to overwrite the file if it exists.
525
526
528
529
See the SageTeX documentation for more details.""" % sys.argv[0])
530
try:
opts, args = getopt.getopt(sys.argv[1:], ’ho’, [’help’, ’overwrite’])
533 except getopt.GetoptError, err:
534
print str(err)
531
532
38
usage()
sys.exit(2)
535
536
537
overwrite = False
for o, a in opts:
540
if o in (’-h’, ’--help’):
541
usage()
542
sys.exit()
543
elif o in (’-o’, ’--overwrite’):
544
overwrite = True
538
539
545
if len(args) == 0 or len(args) > 2:
print(’Error: wrong number of arguments. Make sure to specify options first.\n’)
548
usage()
549
sys.exit(2)
546
547
550
if len(args) == 2 and (os.path.exists(args[1]) and not overwrite):
print(’Error: %s exists and overwrite option not specified.’ % args[1])
553
sys.exit(1)
551
552
554
555
src, ext = os.path.splitext(args[0])
All the real work gets done in the line below. Sorry it’s not more exciting-looking.
556
desagetexed = DeSageTex(src)
This part is cool: we need double percent signs at the beginning of the line because
Python needs them (so they get turned into single percent signs) and because
Docstrip needs them (so the line gets passed into the generated file). It’s perfect!
header = """\
%% SageTeX commands have been automatically removed from this file and
559 %% replaced with plain LaTeX. Processed %s.
557
558
560
561
""" % time.strftime(’%a %d %b %Y %H:%M:%S’, time.localtime())
562
if len(args) == 2:
dest = open(args[1], ’w’)
565 else:
566
dest = sys.stdout
563
564
567
568
569
dest.write(header)
dest.write(desagetexed.result)
7.2
extractsagecode.py
Same idea as makestatic.py, except this does basically the opposite thing.
import sys
import time
572 import getopt
573 import os.path
574 from sagetexparse import SageCodeExtractor
570
571
39
575
576
577
def usage():
print("""Usage: %s [-h|--help] [-o|--overwrite] inputfile [outputfile]
578
579
Extracts Sage code from ‘inputfile’.
580
‘inputfile’ can include the .tex extension or not. If you provide
‘outputfile’, the results will be written to a file of that name,
583 otherwise the result will be printed to stdout.
581
582
584
585
Specify ‘-o’ or ‘--overwrite’ to overwrite the file if it exists.
586
587
See the SageTeX documentation for more details.""" % sys.argv[0])
588
try:
opts, args = getopt.getopt(sys.argv[1:], ’ho’, [’help’, ’overwrite’])
591 except getopt.GetoptError, err:
592
print str(err)
593
usage()
594
sys.exit(2)
589
590
595
overwrite = False
for o, a in opts:
598
if o in (’-h’, ’--help’):
599
usage()
600
sys.exit()
601
elif o in (’-o’, ’--overwrite’):
602
overwrite = True
596
597
603
if len(args) == 0 or len(args) > 2:
print(’Error: wrong number of arguments. Make sure to specify options first.\n’)
606
usage()
607
sys.exit(2)
604
605
608
if len(args) == 2 and (os.path.exists(args[1]) and not overwrite):
print(’Error: %s exists and overwrite option not specified.’ % args[1])
611
sys.exit(1)
609
610
612
src, ext = os.path.splitext(args[0])
sagecode = SageCodeExtractor(src)
615 header = """\
616 # This file contains Sage code extracted from %s%s.
617 # Processed %s.
613
614
618
619
""" % (src, ext, time.strftime(’%a %d %b %Y %H:%M:%S’, time.localtime()))
620
if len(args) == 2:
dest = open(args[1], ’w’)
623 else:
624
dest = sys.stdout
621
622
40
625
626
627
dest.write(header)
dest.write(sagecode.result)
7.3
The parser module
Here’s the module that does the actual parsing and replacing. It’s really quite simple, thanks to the awesome Pyparsing module. The parsing code below is nearly
self-documenting! Compare that to fancy regular expressions, which sometimes
look like someone sneezed punctuation all over the screen.
628
629
import sys
from pyparsing import *
First, we define this very helpful parser: it finds the matching bracket, and doesn’t
parse any of the intervening text. It’s basically like hitting the percent sign in
Vim. This is useful for parsing LATEX stuff, when you want to just grab everything
enclosed by matching brackets.
def skipToMatching(opener, closer):
nest = nestedExpr(opener, closer)
632
nest.setParseAction(lambda l, s, t: l[s:getTokensEndLoc()])
633
return nest
630
631
634
635
636
curlybrackets = skipToMatching(’{’, ’}’)
squarebrackets = skipToMatching(’[’, ’]’)
Next, parser for \sage, \sageplot, and pause/unpause calls:
sagemacroparser = r’\sage’ + curlybrackets(’code’)
sageplotparser = (r’\sageplot’
639
+ Optional(squarebrackets)(’opts’)
640
+ Optional(squarebrackets)(’format’)
641
+ curlybrackets(’code’))
642 sagetexpause = Literal(r’\sagetexpause’)
643 sagetexunpause = Literal(r’\sagetexunpause’)
637
638
With those defined, let’s move on to our classes.
SoutParser
Here’s the parser for the generated .sout file. The code below does all the parsing
of the .sout file and puts the results into a list. Notice that it’s on the order of
10 lines of code—hooray for Pyparsing!
class SoutParser():
def __init__(self, fn):
646
self.label = []
644
645
A label line looks like
\newlabel{@sageinlinehinteger i}{{hbunch of LATEX codei}{}{}{}{}}
which makes the parser definition below pretty obvious. We assign some names to
the interesting bits so the newlabel method can make the hinteger i and hbunch
of LATEX codei into the keys and values of a dictionary. The DeSageTeX class
41
then uses that dictionary to replace bits in the .tex file with their Sage-computed
results.
647
648
649
650
651
parselabel = (r’\newlabel{@sageinline’
+ Word(nums)(’num’)
+ ’}{’
+ curlybrackets(’result’)
+ ’{}{}{}{}}’)
We tell it to ignore comments, and hook up the list-making method.
652
653
parselabel.ignore(’%’ + restOfLine)
parselabel.setParseAction(self.newlabel)
A .sout file consists of one or more such lines. Now go parse the file we were
given.
654
655
656
657
658
try:
OneOrMore(parselabel).parseFile(fn)
except IOError:
print ’Error accessing %s; exiting. Does your .sout file exist?’ % fn
sys.exit(1)
Pyparser’s parse actions get called with three arguments: the string that matched,
the location of the beginning, and the resulting parse object. Here we just add
a new key-value pair to the dictionary, remembering to strip off the enclosing
brackets from the “result” bit.
659
660
DeSageTeX
def newlabel(self, s, l, t):
self.label.append(t.result[1:-1])
Now we define a parser for LATEX files that use SageTEX commands. We assume
that the provided fn is just a basename.
class DeSageTex():
def __init__(self, fn):
663
self.sagen = 0
664
self.plotn = 0
665
self.fn = fn
666
self.sout = SoutParser(fn + ’.sout’)
661
662
Parse \sage macros. We just need to pull in the result from the .sout file and
increment the counter—that’s what self.sage does.
667
668
smacro = sagemacroparser
smacro.setParseAction(self.sage)
Parse the \usepackage{sagetex} line. Right now we don’t support commaseparated lists of packages.
usepackage = (r’\usepackage’
+ Optional(squarebrackets)
671
+ ’{sagetex}’)
672
usepackage.setParseAction(replaceWith(r"""% "\usepackage{sagetex}" line was here:
673 \RequirePackage{verbatim}
674 \RequirePackage{graphicx}
675 \newcommand{\sagetexpause}{\relax}
676 \newcommand{\sagetexunpause}{\relax}"""))
669
670
42
Parse \sageplot macros.
677
678
splot = sageplotparser
splot.setParseAction(self.plot)
The printed environments (sageblock and sageverbatim) get turned into
verbatim environments.
679
680
681
682
beginorend = oneOf(’begin end’)
blockorverb = ’sage’ + oneOf(’block verbatim’)
blockorverb.setParseAction(replaceWith(’verbatim’))
senv = ’\\’ + beginorend + ’{’ + blockorverb + ’}’
The non-printed sagesilent environment gets commented out. We could remove
all the text, but this works and makes going back to SageTEX commands (de-deSageTEXing?) easier.
683
684
685
silent = Literal(’sagesilent’)
silent.setParseAction(replaceWith(’comment’))
ssilent = ’\\’ + beginorend + ’{’ + silent + ’}’
The \sagetexindent macro is no longer relevant, so remove it from the output
(“suppress”, in Pyparsing terms).
686
stexindent = Suppress(r’\setlength{\sagetexindent}’ + curlybrackets)
Now we define the parser that actually goes through the file. It just looks for any
one of the above bits, while ignoring anything that should be ignored.
687
688
689
690
691
doit = smacro | senv | ssilent | usepackage | splot | stexindent
doit.ignore(’%’ + restOfLine)
doit.ignore(r’\begin{verbatim}’ + SkipTo(r’\end{verbatim}’))
doit.ignore(r’\begin{comment}’ + SkipTo(r’\end{comment}’))
doit.ignore(r’\sagetexpause’ + SkipTo(r’\sagetexunpause’))
We can’t use the parseFile method, because that expects a “complete grammar”
in which everything falls into some piece of the parser. Instead we suck in the
whole file as a single string, and run transformString on it, since that will just
pick out the interesting bits and munge them according to the above definitions.
692
693
str = ’’.join(open(fn + ’.tex’, ’r’).readlines())
self.result = doit.transformString(str)
That’s the end of the class constructor, and it’s all we need to do here. You access
the results of parsing via the result string.
We do have two methods to define. The first does the same thing that \ref
does in your LATEX file: returns the content of the label and increments a counter.
694
695
696
def sage(self, s, l, t):
self.sagen += 1
return self.sout.label[self.sagen - 1]
The second method returns the appropriate \includegraphics command. It does
need to account for the default argument.
697
698
699
700
def plot(self, s, l, t):
self.plotn += 1
if len(t.opts) == 0:
opts = r’[width=.75\textwidth]’
43
701
702
703
704
SageCodeExtractor
else:
opts = t.opts[0]
return (r’\includegraphics%s{sage-plots-for-%s.tex/plot-%s}’ %
(opts, self.fn, self.plotn - 1))
This class does the opposite of the first: instead of removing Sage stuff and leaving
only LATEX, this removes all the LATEX and leaves only Sage.
class SageCodeExtractor():
def __init__(self, fn):
707
smacro = sagemacroparser
708
smacro.setParseAction(self.macroout)
705
706
709
710
711
splot = sageplotparser
splot.setParseAction(self.plotout)
Above, we used the general parsers for \sage and \sageplot. We have to redo
the environment parsers because it seems too hard to define one parser object that
will do both things we want: above, we just wanted to change the environment
name, and here we want to suck out the code. Here, it’s important that we find
matching begin/end pairs; above it wasn’t. At any rate, it’s not a big deal to redo
this parser.
712
713
714
715
716
env_names = oneOf(’sageblock sageverbatim sagesilent’)
senv = r’\begin{’ + env_names(’env’) + ’}’ + SkipTo(
r’\end{’ + matchPreviousExpr(env_names) + ’}’)(’code’)
senv.leaveWhitespace()
senv.setParseAction(self.envout)
717
718
719
spause = sagetexpause
spause.setParseAction(self.pause)
720
721
722
sunpause = sagetexunpause
sunpause.setParseAction(self.unpause)
723
724
doit = smacro | splot | senv | spause | sunpause
725
726
727
str = ’’.join(open(fn + ’.tex’, ’r’).readlines())
self.result = ’’
728
729
doit.transformString(str)
730
731
732
733
def macroout(self, s, l, t):
self.result += ’# \\sage{} from line %s\n’ % lineno(l, s)
self.result += t.code[1:-1] + ’\n\n’
734
735
736
737
738
739
def plotout(self, s, l, t):
self.result += ’# \\sageplot{} from line %s:\n’ % lineno(l, s)
if t.format is not ’’:
self.result += ’# format: %s’ % t.format[0][1:-1] + ’\n’
self.result += t.code[1:-1] + ’\n\n’
740
44
741
742
743
744
def envout(self, s, l, t):
self.result += ’# %s environment from line %s:’ % (t.env,
lineno(l, s))
self.result += t.code[0] + ’\n’
745
746
747
748
def pause(self, s, l, t):
self.result += (’# SageTeX (probably) paused on input line %s.\n\n’ %
(lineno(l, s)))
749
750
751
752
8
def unpause(self, s, l, t):
self.result += (’# SageTeX (probably) unpaused on input line %s.\n\n’ %
(lineno(l, s)))
The remote-sagetex script
Here we describe the Python code for remote-sagetex.py. Since its job is to
replicate the functionality of using Sage and sagetex.py, there is some overlap
with the Python module.
The #!/usr/bin/env python line is provided for us by the .ins file’s preamble, so we don’t put it here.
from __future__ import print_function
import json
755 import sys
756 import time
757 import re
758 import urllib
759 import hashlib
760 import os
761 import os.path
762 import shutil
763 import getopt
764 from contextlib import closing
753
754
765
#########################################################################
# You can provide a filename here and the script will read your login
#
768 # information from that file. The format must be:
#
769 #
#
770 # server = ’http://foo.com:8000’
#
771 # username = ’my_name’
#
772 # password = ’s33krit’
#
773 #
#
774 # You can omit one or more of those lines, use " quotes, and put hash
#
775 # marks at the beginning of a line for comments. Command-line args
#
776 # take precedence over information from the file.
#
777 #########################################################################
778 login_info_file = None
# e.g. ’/home/foo/Private/sagetex-login.txt’
766
767
779
780
45
781
usage = """Process a SageTeX-generated .sage file using a remote Sage server.
782
783
Usage: {0} [options] inputfile.sage
784
785
Options:
786
787
788
789
790
791
-h,
-s,
-u,
-p,
-f,
--help:
--server:
--username:
--password:
--file:
print this message
the Sage server to contact
username on the server
your password
get login information from a file
792
793
794
If the server does not begin with the four characters ‘http’, then
‘https://’ will be prepended to the server name.
795
You can hard-code the filename from which to read login information into
the remote-sagetex script. Command-line arguments take precedence over
798 the contents of that file. See the SageTeX documentation for formatting
799 details.
796
797
800
801
802
If any of the server, username, and password are omitted, you will be
asked to provide them.
803
804
805
See the SageTeX documentation for more details on usage and limitations
of remote-sagetex.""".format(sys.argv[0])
806
807
server, username, password = (None,) * 3
808
try:
opts, args = getopt.getopt(sys.argv[1:], ’hs:u:p:f:’,
811
[’help’, ’server=’, ’user=’, ’password=’, ’file=’])
812 except getopt.GetoptError as err:
813
print(str(err), usage, sep=’\n\n’)
814
sys.exit(2)
809
810
815
for o, a in opts:
if o in (’-h’, ’--help’):
818
print(usage)
819
sys.exit()
820
elif o in (’-s’, ’--server’):
821
server = a
822
elif o in (’-u’, ’--user’):
823
username = a
824
elif o in (’-p’, ’--password’):
825
password = a
826
elif o in (’-f’, ’--file’):
827
login_info_file = a
816
817
828
829
830
if len(args) != 1:
print(’Error: must specify exactly one file. Please specify options first.’,
46
831
832
usage, sep=’\n\n’)
sys.exit(2)
833
834
jobname = os.path.splitext(args[0])[0]
When we send things to the server, we get everything back as a string, including
tracebacks. We can search through output using regexps to look for typical traceback strings, but there’s a more robust way: put in a special string that changes
every time and is printed when there’s an error, and look for that. Then it is
massively unlikely that a user’s code could produce output that we’ll mistake for
an actual traceback. System time will work well enough for these purposes. We
produce this string now, and we it when parsing the .sage file (we insert it into
code blocks) and when parsing the output that the remote server gives us.
835
parsedotsage
traceback_str = ’Exception in SageTeX session {0}:’.format(time.time())
To figure out what commands to send the remote server, we actually read in the
.sage file as strings and parse it. This seems a bit strange, but since we know
exactly what the format of that file is, we can parse it with a couple flags and a
handful of regexps.
836
837
def parsedotsage(fn):
with open(fn, ’r’) as f:
Here are the regexps we use to snarf the interesting bits out of the .sage file.
Below we’ll use the re module’s match function so we needn’t anchor any of these
at the beginning of the line.
838
839
840
841
842
843
844
845
846
inline = re.compile(r" _st_.inline\((?P<num>\d+), (?P<code>.*)\)")
plot = re.compile(r" _st_.plot\((?P<num>\d+), (?P<code>.*)\)")
goboom = re.compile(r" _st_.goboom\((?P<num>\d+)\)")
pausemsg = re.compile(r"print.’(?P<msg>SageTeX (un)?paused.*)’")
blockbegin = re.compile(r"_st_.blockbegin\(\)")
ignore = re.compile(r"(try:)|(except):")
in_comment = False
in_block = False
cmds = []
Okay, let’s go through the file. We’re going to make a list of dictionaries. Each
dictionary corresponds to something we have to do with the remote server, except
for the pause/unpause ones, which we only use to print out information for the
user. All the dictionaries have a type key, which obviously tells you type they
are. The pause/unpause dictionaries then just have a msg which we toss out to
the user. The “real” dictionaries all have the following keys:
• type: one of inline, plot, and block.
• goboom: used to help the user pinpoint errors, just like the goboom function
(page 36) does.
• code: the code to be executed.
47
Additionally, the inline and plot dicts have a num key for the label we write to
the .sout file.
Here’s the whole parser loop. The interesting bits are for parsing blocks because
there we need to accumulate several lines of code.
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
for line in f.readlines():
if line.startswith(’"""’):
in_comment = not in_comment
elif not in_comment:
m = pausemsg.match(line)
if m:
cmds.append({’type’: ’pause’,
’msg’: m.group(’msg’)})
m = inline.match(line)
if m:
cmds.append({’type’: ’inline’,
’num’: m.group(’num’),
’code’: m.group(’code’)})
m = plot.match(line)
if m:
cmds.append({’type’: ’plot’,
’num’: m.group(’num’),
’code’: m.group(’code’)})
The order of the next three “if”s is important, since we need the “goboom” line
and the “blockbegin” line to not get included into the block’s code. Note that
the lines in the .sage file already have some indentation, which we’ll use when
sending the block to the server—we wrap the text in a try/except.
m = goboom.match(line)
if m:
cmds[-1][’goboom’] = m.group(’num’)
if in_block:
in_block = False
if in_block and not ignore.match(line):
cmds[-1][’code’] += line
if blockbegin.match(line):
cmds.append({’type’: ’block’,
’code’: ’’})
in_block = True
865
866
867
868
869
870
871
872
873
874
875
876
return cmds
Parsing the .sage file is simple enough so that we can write one function and just
do it. Interacting with the remote server is a bit more complicated, and requires
us to carry some state, so let’s make a class.
RemoteSage
This is pretty simple; it’s more or less a translation of the examples in
sage/server/simple/twist.py.
debug = False
class RemoteSage:
879
def __init__(self, server, user, password):
877
878
48
880
881
882
883
884
885
886
887
888
self._srv = server.rstrip(’/’)
sep = ’___S_A_G_E___’
self._response = re.compile(’(?P<header>.*)’ + sep +
’\n*(?P<output>.*)’, re.DOTALL)
self._404 = re.compile(’404 Not Found’)
self._session = self._get_url(’login’,
urllib.urlencode({’username’: user,
’password’:
password}))[’session’]
In the string below, we want to do “partial formatting”: we format in the traceback
string now, and want to be able to format in the code later. The double braces
get ignored by format() now, and are picked up by format() when we use this
later.
self._codewrap = """try:
{{0}}
891 except:
892
print(’{0}’)
893
traceback.print_exc()""".format(traceback_str)
894
self.do_block("""
895
import traceback
896
def __st_plot__(counter, _p_, format=’notprovided’, **kwargs):
897
if format == ’notprovided’:
898
formats = [’eps’, ’pdf’]
899
else:
900
formats = [format]
901
for fmt in formats:
902
plotfilename = ’plot-%s.%s’ % (counter, fmt)
903
_p_.save(filename=plotfilename, **kwargs)""")
889
890
904
905
906
def _encode(self, d):
return ’session={0}&’.format(self._session) + urllib.urlencode(d)
907
908
909
910
911
912
913
914
def _get_url(self, action, u):
with closing(urllib.urlopen(self._srv + ’/simple/’ + action +
’?’ + u)) as h:
data = self._response.match(h.read())
result = json.loads(data.group(’header’))
result[’output’] = data.group(’output’).rstrip()
return result
915
916
917
918
919
920
921
922
923
924
def _get_file(self, fn, cell, ofn=None):
with closing(urllib.urlopen(self._srv + ’/simple/’ + ’file’ + ’?’ +
self._encode({’cell’: cell, ’file’: fn}))) as h:
myfn = ofn if ofn else fn
data = h.read()
if not self._404.search(data):
with open(myfn, ’w’) as f:
f.write(data)
else:
49
925
926
927
print(’Remote server reported {0} could not be found:’.format(
fn))
print(data)
The code below gets stuffed between a try/except, so make sure it’s indented!
928
929
930
931
932
933
934
935
936
937
938
939
940
941
def _do_cell(self, code):
realcode = self._codewrap.format(code)
result = self._get_url(’compute’, self._encode({’code’: realcode}))
if result[’status’] == ’computing’:
cell = result[’cell_id’]
while result[’status’] == ’computing’:
sys.stdout.write(’working...’)
sys.stdout.flush()
time.sleep(10)
result = self._get_url(’status’, self._encode({’cell’: cell}))
if debug:
print(’cell: <<<’, realcode, ’>>>’, ’result: <<<’,
result[’output’], ’>>>’, sep=’\n’)
return result
942
943
944
def do_inline(self, code):
return self._do_cell(’ print(latex({0}))’.format(code))
945
946
947
948
949
950
def do_block(self, code):
result = self._do_cell(code)
for fn in result[’files’]:
self._get_file(fn, result[’cell_id’])
return result
951
952
953
954
955
956
def do_plot(self, num, code, plotdir):
result = self._do_cell(’ __st_plot__({0}, {1})’.format(num, code))
for fn in result[’files’]:
self._get_file(fn, result[’cell_id’], os.path.join(plotdir, fn))
return result
When using the simple server API, it’s important to log out so the server doesn’t
accumulate idle sessions that take up lots of memory. We define a close() method
and use this class with the closing context manager that always calls close()
on the way out.
957
958
959
960
961
def close(self):
sys.stdout.write(’Logging out of {0}...’.format(server))
sys.stdout.flush()
self._get_url(’logout’, self._encode({}))
print(’done’)
Next we have a little pile of miscellaneous functions and variables that we want to
have at hand while doing our work. Note that we again use the traceback string
in the error-finding regular expression.
962
963
def do_plot_setup(plotdir):
printc(’initializing plots directory...’)
50
964
965
966
967
if os.path.isdir(plotdir):
shutil.rmtree(plotdir)
os.mkdir(plotdir)
return True
968
969
970
did_plot_setup = False
plotdir = ’sage-plots-for-’ + jobname + ’.tex’
971
972
973
def labelline(n, s):
return r’\newlabel{@sageinline’ + str(n) + ’}{{’ + s
+ ’}{}{}{}{}}\n’
974
def printc(s):
print(s, end=’’)
977
sys.stdout.flush()
975
976
978
979
error = re.compile("(^" + traceback_str + ")|(^Syntax Error:)", re.MULTILINE)
980
def check_for_error(string, line):
if error.search(string):
983
print("""
984 **** Error in Sage code on line {0} of {1}.tex!
985 {2}
986 **** Running Sage on {1}.sage failed! Fix {1}.tex and try again.""".format(
987
line, jobname, string))
988
sys.exit(1)
981
982
Now let’s actually start doing stuff.
989
990
print(’Processing Sage code for {0}.tex using remote Sage server.’.format(
jobname))
991
if login_info_file:
with open(login_info_file, ’r’) as f:
994
print(’Reading login information from {0}.’.format(login_info_file))
995
get_val = lambda x: x.split(’=’)[1].strip().strip(’\’"’)
996
for line in f:
997
print(line)
998
if not line.startswith(’#’):
999
if line.startswith(’server’) and not server:
1000
server = get_val(line)
1001
if line.startswith(’username’) and not username:
1002
username = get_val(line)
1003
if line.startswith(’password’) and not password:
1004
password = get_val(line)
992
993
1005
1006
1007
if not server:
server = raw_input(’Enter server: ’)
1008
1009
1010
if not server.startswith(’http’):
server = ’https://’ + server
1011
51
1012
1013
if not username:
username = raw_input(’Enter username: ’)
1014
if not password:
from getpass import getpass
1017
password = getpass(’Please enter password for user {0} on {1}: ’.format(
1018
username, server))
1015
1016
1019
printc(’Parsing {0}.sage...’.format(jobname))
cmds = parsedotsage(jobname + ’.sage’)
1022 print(’done.’)
1020
1021
1023
1024
1025
sout = ’% This file was *autogenerated* from the file {0}.sage.\n’.format(
os.path.splitext(jobname)[0])
1026
printc(’Logging into {0} and starting session...’.format(server))
with closing(RemoteSage(server, username, password)) as sage:
1029
print(’done.’)
1030
for cmd in cmds:
1031
if cmd[’type’] == ’inline’:
1032
printc(’Inline formula {0}...’.format(cmd[’num’]))
1033
result = sage.do_inline(cmd[’code’])
1034
check_for_error(result[’output’], cmd[’goboom’])
1035
sout += labelline(cmd[’num’], result[’output’])
1036
print(’done.’)
1037
if cmd[’type’] == ’block’:
1038
printc(’Code block begin...’)
1039
result = sage.do_block(cmd[’code’])
1040
check_for_error(result[’output’], cmd[’goboom’])
1041
print(’end.’)
1042
if cmd[’type’] == ’plot’:
1043
printc(’Plot {0}...’.format(cmd[’num’]))
1044
if not did_plot_setup:
1045
did_plot_setup = do_plot_setup(plotdir)
1046
result = sage.do_plot(cmd[’num’], cmd[’code’], plotdir)
1047
check_for_error(result[’output’], cmd[’goboom’])
1048
print(’done.’)
1049
if cmd[’type’] == ’pause’:
1050
print(cmd[’msg’])
1051
if int(time.time()) % 2280 == 0:
1052
printc(’Unscheduled offworld activation; closing iris...’)
1053
time.sleep(1)
1054
print(’end.’)
1027
1028
1055
with open(jobname + ’.sage’, ’r’) as sagef:
h = hashlib.md5()
1058
for line in sagef:
1059
if (not line.startswith(’ _st_.goboom’) and
1060
not line.startswith("print ’SageT")):
1061
h.update(line)
1056
1057
52
Putting the {1} in the string, just to replace it with %, seems a bit weird, but if I
put a single percent sign there, Docstrip won’t put that line into the resulting .py
file—and if I put two percent signs, it replaces them with \MetaPrefix which is
## when this file is generated. This is a quick and easy workaround.
sout += """%{0}% md5sum of corresponding .sage file
{1} (minus "goboom" and pause/unpause lines)
1064 """.format(h.hexdigest(), ’%’)
1062
1063
1065
printc(’Writing .sout file...’)
with open(jobname + ’.sout’, ’w’) as soutf:
1068
soutf.write(sout)
1069
print(’done.’)
1070 print(’Sage processing complete. Run LaTeX on {0}.tex again.’.format(jobname))
1066
1067
9
Credits and acknowledgments
According to the original README file, this system was originally done by Gonzalo Tornaria and Joe Wetherell. Later Harald Schilly made some improvements
and modifications. Many of the examples in the example.tex file are from Harald.
Dan Drake rewrote and extended the style file (there is effectively zero original
code there), made significant changes to the Python module, put both files into
Docstrip format, and wrote all the documentation and extra Python scripts.
Many thanks to Jason Grout for his numerous comments, suggestions, and
feedback. Thanks to Nicolas Thiéry for the initial code and contributions to the
sageexample environment.
10
Copying and licenses
If you are unnaturally curious about the current state of the SageTEX package, you
can visit http://www.bitbucket.org/ddrake/sagetex/. There is a Mercurial
repository and other stuff there.
As for the terms and conditions under which you can copy and modify SageTEX:
The source code of the SageTEX package may be redistributed and/or modified
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 2 of the License, or (at your option) any later
version. To view a copy of this license, see http://www.gnu.org/licenses/ or
send a letter to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
The documentation of the SageTEX package is licensed under the Creative
Commons Attribution-Share Alike 3.0 License. To view a copy of this license,
visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105,
USA.
I am not terribly dogmatic about these licenses, so if you would like to do
something with SageTEX that’s not possible under these license conditions, please
53
contact me. I will likely be receptive to suggestions.
54
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertising