Anonymous avatar Anonymous committed 8594da7

Initial checkin of remote-sagetex.py.

Comments (0)

Files changed (5)

 makestatic.py
 extractsagecode.py
 sagetexparse.py
+remote-sagetex.py
 auto/*.el
 dist/*
 MANIFEST
 \documentclass{article}
 \title{Examples of embedding Sage in \LaTeX{} with \textsf{Sage\TeX}}
 \author{Dan Drake and others}
-\usepackage{amsmath} 
+\usepackage{amsmath}
 \usepackage{sagetex}
-% 
+%
 % If you want SageTeX to use Imagemagick's `convert' utility to make eps
 % files from png files when generating a dvi file, add the "imagemagick"
 % option above:
 You can't do assignment inside \verb|\sage| macros, since Sage doesn't
 know how to typeset the output of such a thing. So you have to use a
 code block. The elliptic curve $E$ given by $\sage{E}$ has discriminant
-$\sage{E.discriminant()}$. 
+$\sage{E.discriminant()}$.
 
 You can do anything in a code block that you can do in Sage and/or
 Python. Here we save an elliptic curve into a file.
     E.save('E2')
 \end{sageblock}
 
-The 9999th Fourier coefficient of $\sage{E}$ is 
-$\sage{E.anlist(100000)[9999]}$. 
+The 9999th Fourier coefficient of $\sage{E}$ is
+$\sage{E.anlist(100000)[9999]}$.
 
 The following code block doesn't appear in the typeset file\dots
 \begin{sagesilent}

remote-sagetex.dtx

+% \section{The \texttt{remote-sagetex} script}
+% \label{sec:remote-sagetex-code}
+%
+% Here we describe the Python code for |remote-sagetex|
+%
+% \iffalse
+%<*remotesagetex>
+% \fi
+%
+% 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.
+%    \begin{macrocode}
+# You can fill in your information here, or you can set any of these to
+# None and the script will ask you to provide them.
+server = 'http://localhost:8000'
+username = 'admin'
+password = 'password'
+
+import sys
+try:
+    import json
+except ImportError:
+    print 'You need Python 2.6 or later to run this! Exiting.'
+    sys.exit(1)
+import time
+import re
+import urllib
+import hashlib
+import os
+import os.path
+import shutil
+import getopt
+from contextlib import contextmanager
+
+def usage():
+    print("""Process a SageTeX-generated .sage file using a remote Sage server.
+
+Usage: {0} [options] inputfile.sage
+
+Options:
+
+    -h, --help:         print this message
+    -s, --server:       the Sage server to contact
+    -u, --username:     username on the server
+    -p, --password:     your password
+
+If the server does not begin with the four characters `http', then
+`https://' will be prepended to the server name.
+
+You can hard-code the server, username, and password values in the
+remote-sagetex script. If any are omitted, you will be asked to provide
+them.
+
+See the SageTeX documentation for more details on usage and limitations
+of remote-sagetex.""".format(sys.argv[0]))
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], 'hs:u:p:',
+                               ['help', 'server=', 'user=', 'password='])
+except getopt.GetoptError, err:
+    print(str(err))
+    usage()
+    sys.exit(2)
+
+for o, a in opts:
+    if o in ('-h', '--help'):
+        usage()
+        sys.exit()
+    elif o in ('-s', '--server'):
+        server = a
+    elif o in ('-u', '--user'):
+        username = a
+    elif o in ('-p', '--password'):
+        password = a
+
+if len(args) != 1:
+    print('Error: must specify exactly one file. Please specify options first.\n')
+    usage()
+    sys.exit(2)
+
+jobname = os.path.splitext(args[0])[0]
+
+def parsedotsage(fn):
+    with open(fn, 'r') as f:
+        ignore = re.compile(r"""(\#\#.This.file.*was.\*autogenerated)|
+                                (import.sagetex)|
+                                (_st_.=.sagetex)|
+                                (_st_.blockend\(\))|
+                                (_st_.useimagemagick = True)|
+                                (_st_.useepstopdf = True)|
+                                (try:)|
+                                (except:)""", re.VERBOSE)
+        goboom = re.compile(r" _st_.goboom\((?P<num>\d+)\)")
+        pausemsg = re.compile(r"print '(?P<msg>SageTeX (un)?paused.*)'")
+        inline = re.compile(r" _st_.inline\((?P<num>\d+), (?P<code>.*)\)")
+        blockbegin = re.compile(r"_st_.blockbegin\(\)")
+        ws = re.compile(r"(?P<indent>\s+)")
+        plot = re.compile(r" _st_.plot\((?P<num>\d+), (?P<code>.*)\)")
+        in_comment = False
+        in_block = False
+        cmds = []
+        for line in f.readlines():
+            if not ignore.match(line):
+                if line[:-1] == '"""':
+                    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')})
+                    m = goboom.match(line)
+                    if m:
+                        cmds[-1]['goboom'] = m.group('num')
+                        if in_block:
+                            in_block = False
+                    if in_block:
+                        if cmds[-1]['indent'] == 0:
+                            # this is the first line of the block, so
+                            # establish indentation to remove. We know it
+                            # must be at least 1.
+                            cmds[-1]['indent'] = len(ws.match(line).group('indent'))
+                        cmds[-1]['cell'] += line[cmds[-1]['indent']:]
+                    if blockbegin.match(line):
+                        cmds.append({'type': 'block',
+                                     'cell': '',
+                                     'indent': 0})
+                        in_block = True
+    return cmds
+
+debug = False
+
+class RemoteSage:
+    def __init__(self, server, user, password):
+        self.srv = server
+        sep = '___S_A_G_E___'
+        self.response = re.compile('(?P<header>.*)' + sep +
+                                   '\n*(?P<output>.*)', re.DOTALL)
+        self.session = self.get_url('login',
+                                    urllib.urlencode({'username': user,
+                                    'password':
+                                    password}))['session']
+        self.did_plot_setup = False
+
+
+    def encode(self, d):
+        return 'session={0}&'.format(self.session) + urllib.urlencode(d)
+
+    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
+
+    def get_file(self, fn, cell, ofn=None):
+        with closing(urllib.urlopen(server + '/simple/' + 'file' + '?' +
+                     self.encode({'cell': cell, 'file': fn}))) as h:
+            myfn = ofn if ofn else fn
+            with open(myfn, 'w') as f:
+                f.write(h.read())
+
+    def do_cell(self, code):
+        result = self.get_url('compute', self.encode({'code': code}))
+        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: <<<')
+            print(code)
+            print('>>>')
+            print('result: <<<')
+            print(result['output'])
+            print('>>>')
+        return result
+
+    def do_inline(self, code):
+        return self.do_cell('latex({0})'.format(code))
+
+    def do_block(self, code):
+        result = self.do_cell(code)
+        for fn in result['files']:
+            self.get_file(fn, result['cell_id'])
+        return result
+
+    def do_plot(self, num, code, plotdir):
+        if not self.did_plot_setup:
+            self.do_block("""
+def __st_plot__(counter, _p_, format='notprovided', **kwargs):
+    if format == 'notprovided':
+        formats = ['eps', 'pdf']
+    else:
+        formats = [format]
+    for fmt in formats:
+        plotfilename = 'plot-%s.%s' % (counter, fmt)
+        _p_.save(filename=plotfilename, **kwargs)""")
+            self.did_plot_setup = True
+        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
+
+    def close(self):
+        sys.stdout.write('Logging out of {0}...'.format(server))
+        sys.stdout.flush()
+        self.get_url('logout', self.encode({}))
+        print('done')
+
+# is it just me, or does the function definition read like something by
+# Dr. Seuss?
+@contextmanager
+def closing(thing):
+    try:
+        yield thing
+    finally:
+        thing.close()
+
+def do_plot_setup(plotdir):
+    print('Initializing plots directory')
+    if os.path.isdir(plotdir):
+        shutil.rmtree(plotdir)
+    os.mkdir(plotdir)
+    return True
+
+def labelline(n, s):
+    return r'\newlabel{@sageinline' + str(n) + '}{{' + s  + '}{}{}{}{}}\n'
+
+def progress(s, linebreak=True):
+    if linebreak:
+        print(s)
+    else:
+        sys.stdout.write(s)
+        sys.stdout.flush()
+
+did_plot_setup = False
+plotdir = 'sage-plots-for-' + jobname + '.tex'
+
+error = re.compile(r"(^Traceback \(most recent call last\):)|" +
+                   r"(^SyntaxError:)", re.MULTILINE)
+def check_for_error(string, line):
+    if error.search(string):
+        print('\n**** Error in Sage code on line {0} of {1}.tex!'.format(
+                line, jobname))
+        print(string)
+        print('\n**** Running Sage on {0}.sage failed! Fix {0}.tex and try again.'.format(jobname))
+        sys.exit(1)
+
+# okay, now we start actually doing stuff
+
+progress('Processing Sage code for {0}.tex using remote Sage server.'.format(
+    jobname))
+
+if not server:
+    server = raw_input('Enter server: ')
+
+if not username:
+    username = raw_input('Enter username: ')
+
+if not password:
+    from getpass import getpass
+    password = getpass('Please enter password for user {0} on {1}: '.format(
+        username, server))
+
+if server[:4] != 'http':
+    server = 'https://' + server
+
+progress('Parsing {0}.sage...'.format(jobname), False)
+cmds = parsedotsage(jobname + '.sage')
+progress('done.')
+
+sout = '% This file was *autogenerated* from the file {0}.sage.\n'.format(
+    os.path.splitext(jobname)[0])
+
+progress('Logging into {0} and starting session...'.format(server), False)
+with closing(RemoteSage(server, username, password)) as sage:
+    progress('done.')
+    for cmd in cmds:
+        if cmd['type'] == 'inline':
+            progress('Inline formula {0}...'.format(cmd['num']), False)
+            result = sage.do_inline(cmd['code'])
+            check_for_error(result['output'], cmd['goboom'])
+            sout += labelline(cmd['num'], result['output'])
+            progress('done.')
+        if cmd['type'] == 'block':
+            progress('Code block begin...'.format(cmd['goboom']), False)
+            result = sage.do_block(cmd['cell'])
+            check_for_error(result['output'], cmd['goboom'])
+            progress('end.')
+        if cmd['type'] == 'plot':
+            progress('Plot {0}...'.format(cmd['num']), False)
+            if not did_plot_setup:
+                did_plot_setup = do_plot_setup(plotdir)
+            result = sage.do_plot(cmd['num'], cmd['code'], plotdir)
+            check_for_error(result['output'], cmd['goboom'])
+            progress('done.')
+        if cmd['type'] == 'pause':
+            progress(cmd['msg'])
+        if int(time.time()) % 2280 == 0:
+            progress('Unscheduled offworld activation; closing iris...', False)
+            time.sleep(1)
+            progress('done.')
+
+with open(jobname + '.sage', 'r') as sagef:
+    h = hashlib.md5()
+    for line in sagef:
+        if line[:12] != ' _st_.goboom' and line[:12] != "print 'SageT":
+            h.update(line)
+    sout += """%{0}% md5sum of corresponding .sage file
+% (minus "goboom" and pause/unpause lines)
+""".format(h.hexdigest())
+
+progress('Writing .sout file...', False)
+with open(jobname + '.sout', 'w') as soutf:
+    soutf.write(sout)
+    progress('done.')
+progress('Sage processing complete. Run LaTeX on {0}.tex again.'.format(jobname))
+%    \end{macrocode}
+
+% \endinput
+%</remotesagetex>
+% Local Variables: 
+% mode: doctex
+% TeX-master: "sagetexpackage"
+% End: 

sagetexpackage.dtx

 % to say:
 %
 %   This work has the LPPL maintenance status `maintained'.
-%   
+%
 %   The Current Maintainer of this work is Dan Drake.
 %
 %   This work consists of the files sagetexpackage.dtx,
 %   sagetexpackage.ins, example.tex, and the derived files sagetex.sty,
 %   sagetex.py, sagetexparse.py, makestatic.py, and extractsagecode.py.
-% 
+%
 % \fi
 %
 % \iffalse
 
 \renewcommand{\subsubsectionautorefname}{section}
 \renewcommand{\subsectionautorefname}{section}
-\EnableCrossrefs         
+\EnableCrossrefs
 \CodelineIndex
 \RecordChanges
 \begin{document}
 
 \DocInput{py-and-sty.dtx}
 \DocInput{scripts.dtx}
+\DocInput{remote-sagetex.dtx}
 \Finale
 \PrintChanges
 \PrintIndex
 %</driver>
 % \fi
 %
-% \CheckSum{315}
+% \CheckSum{341}
 %
 % \CharacterTable
 %  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
 % \GetFileInfo{sagetex.sty}
 %
 % \DoNotIndex{\newcommand,\newenvironment,\the}
-% 
+%
 % \newcommand{\ST}{\textsf{Sage\TeX}\xspace}
 % \iffalse
 % so I don't have to put \ or {} after \LaTeX:
 % You can also get \ST from \href{http://tug.ctan.org/pkg/sagetex}{its
 % CTAN page}. This is not the recommended way to get \ST, but it will
 % work.
-% 
+%
 % If you get \ST from CTAN, you will need to make the |sagetex.sty| file
 % available to \LTX using any of the methods described above, and you
 % will also need to make |sagetex.py| known to Sage. You can either keep
 % \subsection{Using \TeX Shop}
 % \label{sec:using-texshop}
 % \changes{v2.0.1}{2009/03/10}{Add \TeX Shop info}
-% 
+%
 % Starting with version 2.25,
 % \href{http://www.uoregon.edu/~koch/texshop/}{\TeX Shop} includes
 % support for \ST. If you move the file |sage.engine| from
 %
 % \section{Usage}
 % \label{sec:usage}
-% 
+%
 % Let's begin with a rough description of how \ST 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 \LTX on
 % will pull in all the results of Sage's computation.
 %
 % All you really need to know is that to typeset your document, you need
-% to run \LTX, then run Sage, then run \LTX again. 
+% to run \LTX, then run Sage, then run \LTX again.
 %
 % 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
 % \begin{center}
 %   \framebox[2cm]{\rule[-1cm]{0cm}{2cm}\textbf{??}}
 % \end{center}
-% 
+%
 % \noindent That's supposed to resemble the image-not-found graphics
 % used by web browsers and use the traditional ``\textbf{??}'' that \LTX
 % uses to indicate missing references.
 % specify one that Tachyon does not understand, it will produce files in
 % the Targa format with an incorrect extension and \LTX (both |latex|
 % and |pdflatex|) will be profoundly confused. Don't do that.
-% 
+%
 % Since |latex| does not support PNGs, when using 3D plotting (and
 % therefore a bitmap format like PNG), \ST will always issue a warning
 % about incompatible graphics if you use |latex|, provided you've
 %   *don't* prefix the lines with percent signs, those lines get written
 %   into the .sty or .py file. It's just too tricky to get docstrip and
 %   the verbatim stuff to play nicely together. I'd have to redefine how
-%   those environments work, and get them to strip off initial percents. 
+%   those environments work, and get them to strip off initial percents.
 % \fi
-% 
+%
 % \DescribeEnv{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
 % verbatim-like environments: the indentation. The \ST 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. 
+% with |\setlength|: do |\setlength{\sagetexindent}{6ex}| or whatever.
 %
 % \subsection{Pausing \ST}
 % \label{sec:pausing-st-usage}
 % 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 \LTX.
-% 
+%
 % Its usage is the same as |makestatic.py|.
 %
 % Note that the resulting file will almost certainly \emph{not} be a
 % 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.
+%
+% \section{Using \ST without Sage installed}
+% \label{sec:remote-sagetex}
+%
+% foo
 
 % \iffalse
-% Local Variables: 
+% Local Variables:
 % mode: doctex
 % TeX-master: t
 % End:

sagetexpackage.ins

 %% it 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.
-%% 
+%%
 %% This program is distributed in the hope that it will be useful, but
 %% WITHOUT ANY WARRANTY; without even the implied warranty of
 %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 %% General Public License for more details.
-%%         
+%%
 %% You should have received a copy of the GNU General Public License
 %% along with this program.  If not, see <http://www.gnu.org/licenses/>
 
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.
-        
+
 You should have received a copy of the GNU General Public License along
 with this program.  If not, see <http://www.gnu.org/licenses/>
 
 \generate{\file{makestatic.py}{\from{scripts.dtx}{staticscript}}}
 \generate{\file{extractsagecode.py}{\from{scripts.dtx}{extractscript}}}
 
+\generate{\file{remote-sagetex.py}{\from{remote-sagetex.dtx}{remotesagetex}}}
+
 \obeyspaces
 \Msg{******************************************************************}
 \Msg{*                                                                *}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.