##
## This is file `sagetex.py',
## generated with the docstrip utility.
##
## The original source files were:
##
## sagetex.dtx  (with options: `python')
## py-and-sty.dtx  (with options: `python')
## 
## This is a generated file. It is part of the SageTeX package.
## 
## Copyright (C) 2008--2015 by Dan Drake <dr.dan.drake@gmail.com>
## 
## This program is free software: you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published by the
## Free Software Foundation, either version 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/>.
## 
__version__ = """
  [2015/08/26 v3.0-92d9f7a embedding Sage into LaTeX documents]
""".strip()
pyversion = ' '.join(__version__.strip('[').split()[0:2])
from sage.misc.latex import latex
from sage.repl.preparse import preparse
import sys
import os
import os.path
import hashlib
import traceback
import subprocess
import shutil
import re
from collections import defaultdict
class VersionError(Exception):
    pass
class MyDict(defaultdict):
    def __init__(self, *args, **kwargs):
        defaultdict.__init__(self, *args, **kwargs)
        self.default_factory = lambda: -1

    def increment(self, key):
        self[key] = self[key] + 1
def joinone(j, xs_):
    if len(xs_) >= 2:
        xs = ([xs_[0].rstrip(j)] +
              [x.strip(j) for x in xs_[1:-1]] +
              [xs_[-1].lstrip(j)])
    else:
        xs = xs_
    return j.join(xs)
def strip_common_leading_spaces(s):
    lines = s.splitlines()
    lead = min(m.end() for m in
                  [re.match(' *\S', line) for line in lines]
                  if m is not None) - 1
    return '\n'.join(line[lead:] for line in lines)
class SageTeXProcessor():
  def __init__(self, jobname, version=None, version_check=True):
    if version != pyversion:
      errstr = """versions of .sty and .py files do not match.
{0}.sagetex.sage was generated by sagetex.sty version "{1}", but
is being processed by sagetex.py version "{2}".
Please make sure that TeX is using the sagetex.sty
from your current version of Sage; see
http://www.sagemath.org/doc/installation/sagetex.html.""".format(jobname,
  version, pyversion)
      if version_check:
        raise VersionError, errstr
      else:
        print '**** WARNING! Skipping version check for .sty and .py files, and'
        print errstr
    if ' ' in jobname:
      jobname = jobname.strip('"')
    self.progress('Processing Sage code for {0}.tex...'.format(jobname))
    self.didinitplot = False
    self.useimagemagick = False
    self.useepstopdf = False
    self.plotdir = 'sage-plots-for-' + jobname + '.tex'
    self.filename = jobname
    self.name = os.path.splitext(jobname)[0]
    autogenstr = """% This file was *autogenerated* from {0}.sagetex.sage with
 % sagetex.py version {1}\n""".format(self.name, version)
    self.max_counter_seen = MyDict()
    self.souttmp = open(self.filename + '.sagetex.sout.tmp', 'w')
    self.souttmp.write(autogenstr)
    self.scmdtmp = open(self.filename + '.sagetex.scmd.tmp', 'w')
    self.scmdtmp.write(autogenstr)
    self.scmdpos = 3
  def progress(self, t,linebreak=True):
    if linebreak:
      print(t)
    else:
      sys.stdout.write(t)
      sys.stdout.flush()
  def initplot(self):
    self.progress('Initializing plots directory')
    if os.path.isdir(self.plotdir):
      shutil.rmtree(self.plotdir)
    os.mkdir(self.plotdir)
    self.didinitplot = True
  def inline(self, counter, s, labelname='sageinline'):
      if counter <= self.max_counter_seen[labelname]:
          return
      else:
          self.max_counter_seen.increment(labelname)
      if labelname == 'sageinline':
          self.progress('Inline formula {0} (line {1})'.format(counter, self.current_tex_line))
      elif labelname == 'sagecmdline':
          pass # output message already printed
      else:
          raise ValueError, 'inline() got a bad labelname "{0}"'.format(labelname)
      self.souttmp.write(r'\newlabel{@' + labelname + str(counter) +
                         '}{{%\n' + s.rstrip() + '}{}{}{}{}}\n')
  def savecmd(self, s):
      self.scmdtmp.write(s.rstrip() + "\n")
      begin = self.scmdpos
      end = begin + len(s.splitlines()) - 1
      self.scmdpos = end + 1
      return begin, end
  def blockbegin(self):
    self.progress('Code block (line {}) begin...'.format(self.current_tex_line), False)
  def blockend(self):
    self.progress('end')
  def split_sage_cmds(self, s):
      prompt = '\n' + r'\s*sage: '
      cont = r'\s*\.\.\.\.: '
      split = re.split(prompt, '\n' + s)[1:]
      starts = [m.start() - 1 for m in re.finditer(prompt, '\n' + s)]
      starts[0] = re.search(prompt, s).start()
      outputs = []
      for i, j in zip(starts, starts[1:] + [len(s)]):
          k = i + re.match(prompt, s[i:j]).end()
          try:
              k += [m.end() for m in re.finditer(cont, s[k:j])][-1]
          except IndexError:
              pass
          end = s.find('\n', k)
          outputs.append(end)
      ret = []
      for start, end, g in zip(starts, outputs, split):
          lines = g.splitlines()
          cmd = lines[:1]
          for line in lines[1:]:
              m = re.match(cont, line)
              if m:
                  cmd.append(line[m.end():])
          ret.append((start, end, '\n'.join(cmd)))
      return ret
  def doctest(self, counter, s, globals, locals, include_text_output):
      self.progress('Sage example {0} (line {1})'.format(counter, self.current_tex_line))
      splitup = self.split_sage_cmds(s)
      tex_strs = []
      for i in range(len(splitup)):
          boxname = '@sageinline{}-code{}'.format(counter, i)
          to_tmp = [r'\begin{SaveVerbatim}{' + boxname + '}',
                    s[splitup[i][0]:splitup[i][1]]]
          if include_text_output:
              try:
                  to_tmp.append(s[splitup[i][1]:splitup[i+1][0]])
              except IndexError:
                  to_tmp.append(s[splitup[i][1]:])
          to_tmp.append('\\end{SaveVerbatim}\n')
          self.souttmp.write(joinone('\n', to_tmp))
          tex_strs.append(r'\UseVerbatim{' + boxname + '}')
          try:
              result = eval(preparse(splitup[i][2]), globals, locals)
              tex_strs += [r'\abovedisplayskip=0pt plus 3pt ',
                           r'\abovedisplayshortskip=0pt plus 3pt ',
                           r'\begin{displaymath}',
                           latex(result),
                           r' \end{displaymath}']
          except SyntaxError:
              exec preparse(splitup[i][2]) in globals, locals
      self.inline(counter, '\n'.join(tex_strs))
  def commandline(self, counter, s, globals, locals, text_output):
      self.progress('Sage commandline {0} (line {1})'.format(counter, self.current_tex_line))
      scmd_fn = self.name + '.sagetex.scmd'
      if ' ' in scmd_fn:
          scmd_fn = '"{}"'.format(scmd_fn)

      splitup = self.split_sage_cmds(s)
      skip = r'\vspace{\sagecommandlineskip}'
      tex_strs = [skip]
      lstinput = r'\lstinputlisting[firstline={0},lastline={1},firstnumber={2},style=SageInput{escape}]{{{3}}}'
      for i in range(len(splitup)):
          orig_input = s[splitup[i][0]:splitup[i][1]]
          begin, end = self.savecmd(strip_common_leading_spaces(orig_input.strip('\n')))
          if '#@' in orig_input:
              escapeoption = ',escapeinside={\\#@}{\\^^M}'
          else:
              escapeoption = ''
          tex_strs.append(lstinput.format(begin, end, begin - 2, scmd_fn, escape=escapeoption))
          try:
              result = eval(preparse(splitup[i][2]), globals, locals)
              if text_output:
                  begin, end = self.savecmd(str(result))
                  tex_strs.append(lstinput.format(begin, end, begin - 2, scmd_fn, escape=''))
              else:
                  tex_strs.append(r'\begin{displaymath}' +
                                  latex(result) +
                                  r'\end{displaymath}')
          except SyntaxError:
              exec preparse(splitup[i][2]) in globals, locals
      if 'displaymath' not in tex_strs[-1]:
          tex_strs.append(skip)
      self.inline(counter, '\n'.join(tex_strs), labelname='sagecmdline')
  def plot(self, counter, _p_, format='notprovided', **kwargs):
      if not self.didinitplot:
          self.initplot()
      self.progress('Plot {0} (line {1})'.format(counter, self.current_tex_line))
      if format == 'notprovided':
          formats = ['eps', 'pdf']
      else:
          formats = [format]
      for fmt in formats:
          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
          plotfilename = os.path.join(self.plotdir, 'plot-{0}.{1}'.format(counter, fmt))
          try:
              _p_.save(filename=plotfilename, **kwargs)
          except ValueError as inst:
              if re.match('filetype .*not supported by save', str(inst)):
                  newfilename = plotfilename[:-3] + 'png'
                  print '  saving {0} failed; saving to {1} instead.'.format(
                                                    plotfilename, newfilename)
                  _p_.save(filename=newfilename, **kwargs)
                  break
              else:
                  raise
          if format != 'notprovided' and self.useimagemagick:
              self.progress('Calling Imagemagick to convert plot-{0}.{1} to EPS'.format(
                counter, format))
              self.toeps(counter, format)
  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)])
  def goboom(self, line):
    print('\n**** Error in Sage code on line {0} of {1}.tex! Traceback\
 follows.'.format(line, self.filename))
    traceback.print_exc()
    print('\n**** Running Sage on {0}.sage failed! Fix {0}.tex and try\
 again.'.format(self.filename))
    self.souttmp.close()
    os.remove(self.filename + '.sagetex.sout.tmp')
    self.scmdtmp.close()
    os.remove(self.filename + '.sagetex.scmd.tmp')
    sys.exit(int(1))
  def endofdoc(self):
    sagef = open(self.filename + '.sagetex.sage', 'r')
    m = hashlib.md5()
    for line in sagef:
      if not line.startswith((" _st_.goboom",
                              "print 'SageT",
                              "_st_.current_tex_line",
                              " _st_.current_tex_line")):
        m.update(line)
    s = '%' + m.hexdigest() + '% md5sum of corresponding .sage file\
 (minus "goboom", "current_tex_line", and pause/unpause lines)\n'
    self.souttmp.write(s)
    self.scmdtmp.write(s)
    self.souttmp.close()
    os.rename(self.filename + '.sagetex.sout.tmp', self.filename + '.sagetex.sout')
    self.scmdtmp.close()
    os.rename(self.filename + '.sagetex.scmd.tmp', self.filename + '.sagetex.scmd')
    self.progress('Sage processing complete. Run LaTeX on {0}.tex again.'.format(
             self.filename))

