Path: blob/master/src/smc_pyutil/smc_pyutil/sagews2pdf.py
Views: 285
#!/usr/bin/env python21# -*- coding: utf-8 -*-2# This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.3# License: AGPLv3 s.t. "Commons Clause" – read LICENSE.md for details4"""5CONTRIBUTORS:67- William Stein - maintainer and initial author8- Cedric Sodhi - internationalization and bug fixes9- Tomas Kalvoda - internationalization10- Harald Schilly - inkscape svg2pdf, ThreadPool, bug fixes, ...1112"""1314from __future__ import absolute_import, print_function15from .py23 import text_type, HTMLParser, quote, py2decodestr, PY216import sys17if PY2:18# this is a python 2 hack, conciously used to handle the particular situation here, that's all19reload(sys)20sys.setdefaultencoding('utf8')2122MARKERS = {'cell': u"\uFE20", 'output': u"\uFE21"}2324# https://github.com/sagemathinc/cocalc/issues/391025ENGINE = '''26% !TeX program = xelatex27'''2829# ATTN styles have to start with a newline30STYLES = {31'classic':32r"""33\documentclass{article}34\usepackage{fullpage}35\usepackage[utf8x]{inputenc}36\usepackage[T1]{fontenc}37\usepackage{amsmath}38\usepackage{amssymb}39""",40'modern':41r"""42\documentclass[43paper=A4,44pagesize,45fontsize=11pt,46%headings=small,47titlepage=false,48fleqn,49toc=flat,50bibliography=totoc, %totocnumbered,51index=totoc,52listof=flat]{scrartcl}53\usepackage{scrhack}54\setuptoc{toc}{leveldown}5556\usepackage[utf8x]{inputenc}57\usepackage[T1]{fontenc}58\usepackage{xltxtra} % xelatex5960\usepackage[61left=3cm,62right=2cm,63top=2cm,64bottom=2cm,65includeheadfoot]{geometry}66\usepackage[automark,headsepline,ilines,komastyle]{scrlayer-scrpage}67\pagestyle{scrheadings}6869\usepackage{fixltx2e}7071\raggedbottom7273% font tweaks74\usepackage{ellipsis,ragged2e,marginnote}75\usepackage{inconsolata}76\renewcommand{\familydefault}{\sfdefault}77\setkomafont{sectioning}{\normalcolor\bfseries}78\setkomafont{disposition}{\normalcolor\bfseries}7980\usepackage{mathtools}81\mathtoolsset{showonlyrefs=true}82\usepackage{amssymb}83\usepackage{sfmath}84"""85}8687COMMON = r"""88\usepackage[USenglish]{babel}89\usepackage{etoolbox}90\usepackage{url}91\usepackage{hyperref}9293% use includegraphics directly, but beware, that this is actually ...94\usepackage{graphicx}95% ... adjust box! http://latex-alive.tumblr.com/post/8148140844996\usepackage[Export]{adjustbox}97\adjustboxset{max size={\textwidth}{0.7\textheight}}9899\usepackage{textcomp}100\def\leftqquote{``}\def\rightqqoute{''}101\catcode`\"=13102\def"{\bgroup\def"{\rightqqoute\egroup}\leftqquote}103104\makeatletter105\preto{\@verbatim}{\topsep=0pt \partopsep=0pt }106\makeatother107108\usepackage{color}109\definecolor{midgray}{rgb}{0.5,0.5,0.5}110\definecolor{lightyellow}{rgb}{1,1,.92}111\definecolor{dblackcolor}{rgb}{0.0,0.0,0.0}112\definecolor{dbluecolor}{rgb}{.01,.02,0.7}113\definecolor{dredcolor}{rgb}{1,0,0}114\definecolor{dbrowncolor}{rgb}{0.625,0.3125,0}115\definecolor{dgraycolor}{rgb}{0.30,0.3,0.30}116\definecolor{graycolor}{rgb}{0.35,0.35,0.35}117118\usepackage{listings}119\lstdefinelanguage{Sage}[]{Python}120{morekeywords={True,False,sage,singular},121sensitive=true}122\lstset{123showtabs=False,124showspaces=False,125showstringspaces=False,126commentstyle={\ttfamily\color{dbrowncolor}},127keywordstyle={\ttfamily\color{dbluecolor}\bfseries},128stringstyle ={\ttfamily\color{dgraycolor}\bfseries},129numberstyle ={\tiny\color{midgray}},130backgroundcolor=\color{lightyellow},131language = Sage,132basicstyle={\ttfamily},133extendedchars=true,134keepspaces=true,135aboveskip=1em,136belowskip=0.1em,137breaklines=true,138prebreak = \raisebox{0ex}[0ex][0ex]{\ensuremath{\backslash}},139%frame=single140}141142% sagemath macros143\newcommand{\Bold}[1]{\mathbb{#1}}144\newcommand{\ZZ}{\Bold{Z}}145\newcommand{\NN}{\Bold{N}}146\newcommand{\RR}{\Bold{R}}147\newcommand{\CC}{\Bold{C}}148\newcommand{\FF}{\Bold{F}}149\newcommand{\QQ}{\Bold{Q}}150\newcommand{\QQbar}{\overline{\QQ}}151\newcommand{\CDF}{\Bold{C}}152\newcommand{\CIF}{\Bold{C}}153\newcommand{\CLF}{\Bold{C}}154\newcommand{\RDF}{\Bold{R}}155\newcommand{\RIF}{\Bold{I} \Bold{R}}156\newcommand{\RLF}{\Bold{R}}157\newcommand{\CFF}{\Bold{CFF}}158\newcommand{\GF}[1]{\Bold{F}_{#1}}159\newcommand{\Zp}[1]{\ZZ_{#1}}160\newcommand{\Qp}[1]{\QQ_{#1}}161\newcommand{\Zmod}[1]{\ZZ/#1\ZZ}162"""163164# this is part of the preamble above, although this time full of utf8 chars165COMMON += text_type(r"""166% mathjax has \lt and \gt167\newcommand{\lt}{<}168\newcommand{\gt}{>}169% also support HTML's ≤ and ≥170\newcommand{\lequal}{≤}171\newcommand{\gequal}{≥}172\newcommand{\notequal}{≠}173174% defining utf8 characters for listings175\lstset{literate=176{á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ú}{{\'u}}1177{Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ú}{{\'U}}1178{à}{{\`a}}1 {è}{{\`e}}1 {ì}{{\`i}}1 {ò}{{\`o}}1 {ù}{{\`u}}1179{À}{{\`A}}1 {È}{{\'E}}1 {Ì}{{\`I}}1 {Ò}{{\`O}}1 {Ù}{{\`U}}1180{ä}{{\"a}}1 {ë}{{\"e}}1 {ï}{{\"i}}1 {ö}{{\"o}}1 {ü}{{\"u}}1181{Ä}{{\"A}}1 {Ë}{{\"E}}1 {Ï}{{\"I}}1 {Ö}{{\"O}}1 {Ü}{{\"U}}1182{â}{{\^a}}1 {ê}{{\^e}}1 {î}{{\^i}}1 {ô}{{\^o}}1 {û}{{\^u}}1183{Â}{{\^A}}1 {Ê}{{\^E}}1 {Î}{{\^I}}1 {Ô}{{\^O}}1 {Û}{{\^U}}1184{œ}{{\oe}}1 {Œ}{{\OE}}1 {æ}{{\ae}}1 {Æ}{{\AE}}1 {ß}{{\ss}}1185{ç}{{\c c}}1 {Ç}{{\c C}}1 {ø}{{\o}}1 {å}{{\r a}}1 {Å}{{\r A}}1186{ã}{{\~a}}1 {Ã}{{\~A}}1 {õ}{{\~o}}1 {Õ}{{\~O}}1187{€}{{\EUR}}1 {£}{{\pounds}}1188}189190""")191192FOOTER = """193%configuration={"latex_command":"xelatex -synctex=1 -interact=nonstopmode 'tmp.tex'"}194"""195196import argparse, base64, json, os, shutil, sys, textwrap, tempfile197from uuid import uuid4198199BASE_URL = os.environ.get("COCALC_URL", "https://cocalc.com")200201202def escape_path(s):203# see http://stackoverflow.com/questions/946170/equivalent-javascript-functions-for-pythons-urllib-quote-and-urllib-unquote204s = quote(text_type(s).encode('utf-8'), safe='~@#$&()*!+=:;,.?/\'')205return s.replace('#', '%23').replace("?", '%3F')206207208def wrap(s, c=90):209return '\n'.join(['\n'.join(textwrap.wrap(x, c)) for x in s.splitlines()])210211212# used in texifyHTML and then again, in tex_escape213# they're mapped to macros, defined in the latex preamble214relational_signs = [215('gt', 'gt'),216('lt', 'lt'),217('ge', 'gequal'),218('le', 'lequal'),219('ne', 'notequal'),220]221222223def tex_escape(s):224replacements = [225('\\', '{\\textbackslash}'),226('_', r'\_'),227('^', r'\^'),228(r'{\textbackslash}$', r'\$'),229('%', r'\%'),230('#', r'\#'),231('&', r'\&'),232]233for rep in replacements:234s = s.replace(*rep)235for rel in relational_signs:236a, b = r'{\textbackslash}%s' % rel[1], r'\%s ' % rel[1]237s = s.replace(a, b)238return s239240241# Parallel computing can be useful for IO bound tasks.242def thread_map(callable, inputs, nb_threads=1):243"""244Computing [callable(args) for args in inputs]245in parallel using `nb_threads` separate *threads* (default: 2).246247This helps a bit with I/O bound tasks and is rather conservative248to avoid excessive memory usage.249250If an exception is raised by any thread, a RuntimeError exception251is instead raised.252"""253print("Doing the following in parallel:\n%s" % ('\n'.join(inputs)))254from multiprocessing.pool import ThreadPool255tp = ThreadPool(nb_threads)256exceptions = []257258def callable_wrap(x):259try:260return callable(x)261except Exception as msg:262exceptions.append(msg)263264results = tp.map(callable_wrap, inputs)265if len(exceptions) > 0:266raise RuntimeError(exceptions[0])267return results268269270# create a subclass and override the handler methods271272273class Parser(HTMLParser):274275def __init__(self, cmds):276HTMLParser.__init__(self)277self.result = ''278self._commands = cmds279self._dont_close_img = False280281def handle_starttag(self, tag, attrs):282if tag == 'h1':283self.result += '\\section{'284elif tag == 'h2':285self.result += '\\subsection{'286elif tag == 'h3':287self.result += '\\subsubsection{'288elif tag == 'i':289self.result += '\\textemph{'290elif tag == 'div' or tag == 'p':291self.result += '\n\n{'292elif tag == 'ul':293self.result += '\\begin{itemize}'294elif tag == 'ol':295self.result += '\\begin{enumerate}'296elif tag == 'hr':297# self.result += '\n\n' + '-'*80 + '\n\n'298self.result += '\n\n' + r'\noindent\makebox[\linewidth]{\rule{\textwidth}{0.4pt}}' + '\n\n'299elif tag == 'li':300self.result += '\\item{'301elif tag == 'strong':302self.result += '\\textbf{'303elif tag == 'em':304self.result += '\\textit{'305elif tag == 'a':306attrs = dict(attrs)307if 'href' in attrs:308self.result += '\\href{%s}{' % attrs['href']309else:310self.result += '\\url{'311elif tag == 'img':312attrs = dict(attrs)313if "src" in attrs:314href = attrs['src']315_, ext = os.path.splitext(href)316ext = ext.lower()317if '?' in ext:318ext = ext[:ext.index('?')]319# create a deterministic filename based on the href320from hashlib import sha1321base = sha1(href).hexdigest()322filename = base + ext323324# href might start with /blobs/ or similar for e.g. octave plots325# in such a case, there is also a file output and we ignore the image in the html326if href[0] == '/':327self._dont_close_img = True328return329else:330href_download = href331332# NOTE --no-check-certificate is needed since this query is done inside333# the cluster, where the cert won't match the local service name.334c = "rm -f '%s'; wget --no-check-certificate '%s' --output-document='%s'" % (335filename, href_download, filename)336if ext == '.svg':337# convert to pdf338c += " && rm -f '%s'; inkscape --without-gui --export-pdf='%s' '%s'" % (339base + '.pdf', base + '.pdf', filename)340filename = base + '.pdf'341self._commands.append(c)342# the choice of 120 is "informed" but also arbitrary343# besides that, if we scale it in sagews, we also have to scale it here344scaling = 1.345if 'smc-image-scaling' in attrs:346try:347# in practice (and if it is set at all) it is most likely 0.66348scaling = float(attrs['smc-image-scaling'])349except:350pass351resolution = int(120. / scaling)352self.result += '\\includegraphics[resolution=%s]{%s}' % (353resolution, filename)354# alternatively, implicit scaling by adjbox and textwidth355# self.result += '\\includegraphics{%s}'%(filename)356else:357# fallback, because there is no src='...'358self.result += '\\verbatim{image: %s}' % str(attrs)359else:360self.result += '{' # fallback361362def handle_endtag(self, tag):363if tag == 'ul':364self.result += '\\end{itemize}'365elif tag == 'ol':366self.result += '\\end{enumerate}'367elif tag == 'hr':368self.result += ''369elif tag == 'img' and self._dont_close_img:370self._dont_close_img = False371self.result += ''372else:373self.result += '}' # fallback374375def handle_data(self, data):376# safe because all math stuff has already been escaped.377# print "handle_data:", data378self.result += tex_escape(data)379380381def sanitize_math_input(s):382from .markdown2Mathjax import sanitizeInput383# it's critical that $$ be first!384delims = [('$$', '$$'), ('\\(', '\\)'), ('\\[', '\\]'),385('\\begin{equation}', '\\end{equation}'),386('\\begin{equation*}', '\\end{equation*}'),387('\\begin{align}', '\\end{align}'),388('\\begin{align*}', '\\end{align*}'),389('\\begin{eqnarray}', '\\end{eqnarray}'),390('\\begin{eqnarray*}', '\\end{eqnarray*}'),391('\\begin{math}', '\\end{math}'),392('\\begin{displaymath}', '\\end{displaymath}')]393394tmp = [((s, None), None)]395for d in delims:396tmp.append((sanitizeInput(tmp[-1][0][0], equation_delims=d), d))397398return tmp399400401def reconstruct_math(s, tmp):402print("s ='%r'" % s)403print("tmp = '%r'" % tmp)404from .markdown2Mathjax import reconstructMath405while len(tmp) > 1:406s = reconstructMath(s, tmp[-1][0][1], equation_delims=tmp[-1][1])407del tmp[-1]408return s409410411def texifyHTML(s):412replacements = [413('“', '``'),414('”', "''"),415('’', "'"),416('&', "&"),417]418for rep in replacements:419s = s.replace(*rep)420for rel in relational_signs:421a, b = '&%s;' % rel[0], r'\%s' % rel[1]422s = s.replace(a, b)423return s424425426def html2tex(doc, cmds):427doc = texifyHTML(doc)428tmp = sanitize_math_input(doc)429parser = Parser(cmds)430# The number of (unescaped) dollars or double-dollars found so far. An even431# number is assumed to indicate that we're outside of math and thus need to432# escape.433parser.dollars_found = 0434parser.feed(tmp[-1][0][0])435return reconstruct_math(parser.result, tmp)436437438def md2html(s):439from markdown2 import markdown440extras = [441'code-friendly', 'footnotes', 'smarty-pants', 'wiki-tables',442'fenced-code-blocks'443]444445tmp = sanitize_math_input(s)446markedDownText = markdown(tmp[-1][0][0], extras=extras)447return reconstruct_math(markedDownText, tmp)448449450def md2tex(doc, cmds):451x = md2html(doc)452#print "-" * 100453#print "md2html:", x454#print "-" * 100455y = html2tex(x, cmds)456#print "html2tex:", y457#print "-" * 100458return y459460461class Cell(object):462463def __init__(self, s):464self.raw = s465v = s.split('\n' + MARKERS['output'])466if len(v) > 0:467w = v[0].split(MARKERS['cell'] + '\n')468n = w[0].lstrip(MARKERS['cell'])469self.input_uuid = n[:36]470self.input_codes = n[36:]471if len(w) > 1:472self.input = w[1]473else:474self.input = ''475else:476self.input_uuid = self.input = ''477if len(v) > 1:478w = v[1].split(MARKERS['output'])479self.output_uuid = w[0] if len(w) > 0 else ''480self.output = []481for x in w[1:]:482if x:483try:484self.output.append(json.loads(x))485except ValueError:486try:487print("**WARNING:** Unable to de-json '%s'" % x)488except:489print("Unable to de-json some output")490else:491self.output = self.output_uuid = ''492493def latex(self):494"""495Returns the latex represenation of this cell along with a list of commands496that should be executed in the shell in order to obtain remote data files,497etc., to render this cell.498"""499self._commands = []500return self.latex_input() + self.latex_output(), self._commands501502def latex_input(self):503if 'i' in self.input_codes: # hide input504return "\\begin{lstlisting}\n\\end{lstlisting}"505if self.input.strip():506return "\\begin{lstlisting}\n%s\n\\end{lstlisting}" % self.input507else:508return ""509510def latex_output(self):511print("BASE_URL", BASE_URL)512s = ''513if 'o' in self.input_codes: # hide output514return s515for x in self.output:516if 'stdout' in x:517s += "\\begin{verbatim}" + wrap(518x['stdout']) + "\\end{verbatim}"519if 'stderr' in x:520s += "{\\color{dredcolor}\\begin{verbatim}" + wrap(521x['stderr']) + "\\end{verbatim}}"522if 'code' in x:523# TODO: for now ignoring that not all code is Python...524s += "\\begin{lstlisting}" + x['code'][525'source'] + "\\end{lstlisting}"526if 'html' in x:527s += html2tex(x['html'], self._commands)528if 'md' in x:529s += md2tex(x['md'], self._commands)530if 'interact' in x:531pass532if 'tex' in x:533val = x['tex']534if 'display' in val:535s += "$$%s$$" % val['tex']536else:537s += "$%s$" % val['tex']538if 'file' in x:539val = x['file']540if 'url' in val:541target = val['url']542filename = os.path.split(target)[-1]543else:544filename = os.path.split(val['filename'])[-1]545target = "%s/blobs/%s?uuid=%s" % (546BASE_URL, escape_path(filename), val['uuid'])547548base, ext = os.path.splitext(filename)549ext = ext.lower()[1:]550# print "latex_output ext", ext551if ext in ['jpg', 'jpeg', 'png', 'eps', 'pdf', 'svg']:552img = ''553i = target.find("/raw/")554if i != -1:555src = os.path.join(os.environ['HOME'], target[i + 5:])556if os.path.abspath(src) != os.path.abspath(filename):557try:558shutil.copyfile(src, filename)559except Exception as msg:560print(msg)561img = filename562else:563# Get the file from remote server564c = "rm -f '%s'; wget --no-check-certificate '%s' --output-document='%s'" % (565filename, target, filename)566# If we succeeded, convert it to a png, which is what we can easily embed567# in a latex document (svg's don't work...)568self._commands.append(c)569if ext == 'svg':570# hack for svg files; in perfect world someday might do something with vector graphics,571# see http://tex.stackexchange.com/questions/2099/how-to-include-svg-diagrams-in-latex572# Now we live in a perfect world, and proudly introduce inkscape as a dependency for SMC :-)573#c += ' && rm -f "%s"; convert -antialias -density 150 "%s" "%s"'%(base+'.png',filename,base+'.png')574# converts the svg file into pdf575c += " && rm -f '%s'; inkscape --without-gui --export-pdf='%s' '%s'" % (576base + '.pdf', base + '.pdf', filename)577self._commands.append(c)578filename = base + '.pdf'579img = filename580# omitting [width=\\textwidth] allows figsize to set displayed size581# see https://github.com/sagemathinc/cocalc/issues/114582s += '{\\centering\n\\includegraphics{%s}\n\\par\n}\n' % img583elif ext == 'sage3d' and 'sage3d' in extra_data and 'uuid' in val:584# render a static image, if available585v = extra_data['sage3d']586#print "KEYS", v.keys()587uuid = val['uuid']588if uuid in v:589#print "TARGET acquired!"590data = v[uuid].pop()591width = min(1, 1.2 * data.get('width', 0.5))592#print "width = ", width593if 'data-url' in data:594# 'data:image/png;base64,iVBOR...'595data_url = data['data-url']596i = data_url.find('/')597j = data_url.find(";")598k = data_url.find(',')599image_ext = data_url[i + 1:j]600image_data = data_url[k + 1:]601assert data_url[j + 1:k] == 'base64'602filename = str(uuid4()) + "." + image_ext603b64 = base64.b64decode(image_data)604open(filename, 'w').write(b64)605s += '\\includegraphics[width=%s\\textwidth]{%s}\n' % (606width, filename)607608else:609if target.startswith('http'):610s += '\\url{%s}' % target611else:612s += '\\begin{verbatim}[' + target + ']\\end{verbatim}'613614return s615616617class Worksheet(object):618619def __init__(self, filename=None, s=None):620"""621The worksheet defined by the given filename or UTF unicode string s.622"""623self._default_title = ''624if filename:625self._filename = os.path.abspath(filename)626else:627self._filename = None628if filename is not None:629self._default_title = filename630self._init_from(py2decodestr(open(filename).read()))631elif s is not None:632self._init_from(s)633else:634raise ValueError("filename or s must be defined")635636def _init_from(self, s):637self._cells = [Cell(x) for x in s.split('\n' + MARKERS['cell'])]638639def __getitem__(self, i):640return self._cells[i]641642def __len__(self):643return len(self._cells)644645def latex_preamble(self,646title='',647author='',648date='',649style='modern',650contents=True):651# The utf8x instead of utf8 below is because of http://tex.stackexchange.com/questions/83440/inputenc-error-unicode-char-u8-not-set-up-for-use-with-latex, which I needed due to approx symbols, etc. causing trouble.652#\usepackage{attachfile}653from datetime import datetime654top = u'% generated by smc-sagews2pdf -- {timestamp}'655top = top.format(timestamp=str(datetime.utcnow()))656s = top + ENGINE + STYLES[style]657s += COMMON658s += text_type(r"\title{%s}" % tex_escape(title) + u"\n")659s += text_type(r"\author{%s}" % tex_escape(author) + u"\n")660if date:661s += text_type(r"\date{%s}" % tex_escape(date) + u"\n")662s += u"\\begin{document}\n"663s += u"\\maketitle\n"664#if self._filename:665# s += "The Worksheet: \\attachfile{%s}\n\n"%self._filename666667if contents:668s += u"\\tableofcontents\n"669return s670671def latex(self,672title='',673author='',674date='',675style='modern',676contents=True):677if not title:678title = self._default_title679commands = []680tex = []681for c in self._cells:682t, cmd = c.latex()683tex.append(t)684if cmd:685commands.extend(cmd)686if commands:687thread_map(os.system, commands)688return self.latex_preamble(title=title,689author=author,690date=date,691style=style,692contents=contents) \693+ '\n'.join(tex) \694+ r"\end{document}" \695+ FOOTER696697698def sagews_to_pdf(filename,699title='',700author='',701date='',702outfile='',703contents=True,704remove_tmpdir=True,705work_dir=None,706style='modern'):707base = os.path.splitext(filename)[0]708if not outfile:709pdf = base + ".pdf"710else:711pdf = outfile712print("converting: %s --> %s" % (filename, pdf))713W = Worksheet(filename)714try:715if work_dir is None:716work_dir = tempfile.mkdtemp()717else:718if not os.path.exists(work_dir):719os.makedirs(work_dir)720if not remove_tmpdir:721print("Temporary directory retained: %s" % work_dir)722cur = os.path.abspath('.')723os.chdir(work_dir)724from codecs import open725open('tmp.tex', 'w', 'utf8').write(726W.latex(title=title,727author=author,728date=date,729contents=contents,730style=style)) #.encode('utf8'))731from subprocess import check_call732check_call('latexmk -pdf -xelatex -f -interaction=nonstopmode tmp.tex',733shell=True)734if os.path.exists('tmp.pdf'):735shutil.move('tmp.pdf', os.path.join(cur, pdf))736print("Created", os.path.join(cur, pdf))737finally:738if work_dir and remove_tmpdir:739shutil.rmtree(work_dir)740else:741print("Leaving latex files in '%s'" % work_dir)742743744def main():745global extra_data, BASE_URL746747parser = argparse.ArgumentParser(748description="convert a sagews worksheet to a pdf file via latex",749formatter_class=argparse.ArgumentDefaultsHelpFormatter)750parser.add_argument("filename",751nargs='+',752help="name of sagews file (required)",753type=str)754parser.add_argument("--author",755dest="author",756help="author name for printout",757type=str,758default="")759parser.add_argument("--title",760dest="title",761help="title for printout",762type=str,763default="")764parser.add_argument("--date",765dest="date",766help="date for printout",767type=str,768default="")769parser.add_argument("--contents",770dest="contents",771help="include a table of contents 'true' or 'false'",772type=str,773default='true')774parser.add_argument(775"--outfile",776dest="outfile",777help=778"output filename (defaults to input file with sagews replaced by pdf)",779type=str,780default="")781parser.add_argument(782"--remove_tmpdir",783dest="remove_tmpdir",784help=785"if 'false' do not delete the temporary LaTeX files and print name of temporary directory",786type=str,787default='true')788parser.add_argument(789"--work_dir",790dest="work_dir",791help=792"if set, then this is used as the working directory where the tex files are generated and it won't be deleted like the temp dir."793)794parser.add_argument(795'--subdir',796dest="subdir",797help=798"if set, the work_dir will be set (or overwritten) to be pointing to a subdirectory named after the file to be converted.",799default='false')800parser.add_argument(801"--extra_data_file",802dest="extra_data_file",803help=804"JSON format file that contains extra data useful in printing this worksheet, e.g., 3d plots",805type=str,806default='')807parser.add_argument("--style",808dest="style",809help="Styling of the LaTeX document",810type=str,811choices=['classic', 'modern'],812default="modern")813parser.add_argument(814"--base_url",815dest="base_url",816help=817"The 'BASE_URL' from where blobs and other files are being downloaded from",818default=BASE_URL)819820args = parser.parse_args()821args.contents = args.contents == 'true'822args.remove_tmpdir = args.remove_tmpdir == 'true'823args.subdir = args.subdir == 'true'824825if args.extra_data_file:826import json827extra_data = json.loads(open(args.extra_data_file).read())828else:829extra_data = {}830831BASE_URL = args.base_url832833remove_tmpdir = args.remove_tmpdir834835curdir = os.path.abspath('.')836for filename in args.filename:837os.chdir(curdir) # stuff below can change away from curdir838839if args.subdir:840from os.path import dirname, basename, splitext, join841dir = dirname(filename)842subdir = '%s-sagews2pdf' % splitext(basename(filename))[0]843work_dir = join(dir, subdir)844remove_tmpdir = False845elif args.work_dir is not None:846work_dir = os.path.abspath(os.path.expanduser(args.work_dir))847remove_tmpdir = False848else:849work_dir = None850851title = py2decodestr(args.title)852author = py2decodestr(args.author)853854from subprocess import CalledProcessError855try:856sagews_to_pdf(filename,857title=title,858author=author,859date=args.date,860outfile=args.outfile,861contents=args.contents,862remove_tmpdir=remove_tmpdir,863work_dir=work_dir,864style=args.style)865# subprocess.check_call might throw866except CalledProcessError as e:867sys.stderr.write('CalledProcessError: %s\n' % e)868exit(1)869870871if __name__ == "__main__":872main()873874875