From 3a22234a28e4850be169d98fba78d90369a4723e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 13 May 2021 14:35:16 +0300 Subject: [PATCH 1/5] Polish the various utility scripts Use argparse for more friendly argument parsing, and rename script files so that they can be put on PATH. Add them as script files in setup.py, and install also sagetexparse.py module. --- .gitignore | 15 ++- Makefile | 15 ++- sagetex.ins | 9 +- scripts.dtx | 349 ++++++++++++++++++++++++++-------------------------- setup.py | 9 +- 5 files changed, 199 insertions(+), 198 deletions(-) diff --git a/.gitignore b/.gitignore index d6d709a..f646cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,15 @@ E2.sobj auto/ dist/ example.synctex.gz -extractsagecode.py makecmds.sty -makestatic.py -remote-sagetex.py -run-sagetex-if-necessary.py +sagetex-extract +sagetex-extract.py +sagetex-makestatic +sagetex-makestatic.py +sagetex-remote +sagetex-remote.py +sagetex-run +sagetex-run.py sage-plots-for-*.tex/ sagetex.glo sagetex.gls @@ -26,9 +30,10 @@ sagetex.idx sagetex.ilg sagetex.ind sagetex.py -sagetex.pyc sagetex.sty sagetexparse.py +*.pyc +__pycache__ .*.sage-history .*.sage-chat MANIFEST diff --git a/Makefile b/Makefile index 7cac65a..a059df8 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,11 @@ pkg=sagetex dtxs=$(wildcard *.dtx) # the subdir stuff makes the tarball have the directory correct srcs=example.tex README sagetex.ins +pyscripts=sagetex-run sagetex-extract sagetex-makestatic sagetex-remote .SUFFIXES: -all: sagetex.sty sagetex.py example.pdf $(pkg).pdf +all: sagetex.sty sagetex.py $(pyscripts) $(pkg).pdf example.pdf # just depend on the .ind file, since we'll make the .gls and .ind together; # TEXOPTS is used by spkg-install to specify nonstopmode when building docs @@ -35,14 +36,18 @@ sagetex.sty: py-and-sty.dtx $(pkg).dtx sagetex.py: py-and-sty.dtx $(pkg).dtx yes | latex $(TEXOPTS) $(pkg).ins -remote-sagetex.py: remote-sagetex.dtx +sagetex-remote.py: remote-sagetex.dtx yes | latex $(TEXOPTS) $(pkg).ins -run-sagetex-if-necessary.py makestatic.py extractsagecode.py sagetexparse.py: scripts.dtx +sagetex-run.py sagetex-extract.py sagetex-makestatic.py sagetexparse.py: scripts.dtx yes | latex $(TEXOPTS) $(pkg).ins +%: %.py + cp -f $< $@ + chmod +x $@ + clean: auxclean - rm -fr sage-plots-for-* E2.sobj *.pyc sagetex.tar.gz sagetex.py sagetex.pyc sagetex.sty makestatic.py sagetexparse.py extractsagecode.py dist MANIFEST remote-sagetex.py auto *_doctest.sage *_doctest.sage.py example-*.table run-sagetex-if-necessary.py __pycache__ + rm -fr sage-plots-for-* E2.sobj *.pyc sagetex.tar.gz sagetex.py sagetexparse.py $(pyscripts) $(addsuffix .py,$(pyscripts)) sagetex.sty dist MANIFEST remote-sagetex.py auto *_doctest.sage *_doctest.sage.py example-*.table __pycache__ auxclean: /bin/bash -c "rm -f {$(pkg),example}.{glo,gls,aux,out,toc,dvi,pdf,ps,log,ilg,ind,idx,fdb_latexmk,sagetex.*}" @@ -54,5 +59,5 @@ test: ./test # make a source distribution, used for building the spkg -dist: sagetex.sty +dist: sagetex.sty $(pypkg) $(pyscripts) python setup.py sdist --formats=gztar diff --git a/sagetex.ins b/sagetex.ins index dd8dabb..bbbbe13 100644 --- a/sagetex.ins +++ b/sagetex.ins @@ -84,7 +84,6 @@ with this program. If not, see . \from{py-and-sty.dtx}{python}}} \generate{\file{sagetexparse.py}{\from{scripts.dtx}{parsermod}}} - \usedir{scripts/sagetex} % Now define a new preamble with the shebang line at the top. @@ -92,10 +91,10 @@ with this program. If not, see . \def\envpypreamble{\hash!/usr/bin/env python^^J\pypreamble} \usepreamble\envpypreamble -\generate{\file{run-sagetex-if-necessary.py}{\from{scripts.dtx}{ifnecessaryscript}}} -\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}}} +\generate{\file{sagetex-run.py}{\from{scripts.dtx}{ifnecessaryscript}}} +\generate{\file{sagetex-makestatic.py}{\from{scripts.dtx}{staticscript}}} +\generate{\file{sagetex-extract.py}{\from{scripts.dtx}{extractscript}}} +\generate{\file{sagetex-remote.py}{\from{remote-sagetex.dtx}{remotesagetex}}} \obeyspaces \Msg{******************************************************************} diff --git a/scripts.dtx b/scripts.dtx index 8f7048d..07096ee 100644 --- a/scripts.dtx +++ b/scripts.dtx @@ -1,27 +1,26 @@ % \section{Included Python scripts} % \label{sec:included-scripts} % -% Here we describe the Python code for |run-sagetex-if-necessary|, and -% also |makestatic.py|, which removes \ST commands to produce a -% ``static'' file, and |extractsagecode.py|, which extracts all the Sage -% code from a |.tex| file. +% Here we describe the Python code for \ST scripts, for running +% Sage only if necessary, substituting in Sage outputs to produce +% a ``static'' file, and extracting all Sage code from a |.tex| file. % -% \subsection{run-sagetex-if-necessary} -% \label{sec:run-sagetex-if-necessary} +% \subsection{\texttt{sagetex-run}} +% \label{sec:sagetex-run} % \iffalse %<*ifnecessaryscript> % \fi % % When working on a document that uses \ST, running Sage every time you % typeset your document may take too long, especially since it often -% won't be necessary. This script is a drop-in replacement for Sage: +% is not necessary. This script is a drop-in replacement for Sage: % instead of % \begin{center} % |sage document.sagetex.sage| % \end{center} % you can do % \begin{center} -% |run-sagetex-if-necessary.py document.sagetex.sage| +% |sagetex-run document.sagetex.sage| % \end{center} % and it will use the MD5 mechanism described in the |endofdoc| macro % (page~{\pageref{macro:endofdoc}). With this, you can set up your editor @@ -29,105 +28,105 @@ % that does % \begin{quote} % |pdflatex $1|\\ -% |run-sagetex-if-necessary.py $1| +% |sagetex-run $1| % \end{quote} % which will only, of course, run Sage when necessary. % \begin{macrocode} - -# given a filename f, examines f.sagetex.sage and f.sagetex.sout and -# runs Sage if necessary. +""" +Given a filename f, examines f.sagetex.sage and f.sagetex.sout and +runs Sage if necessary. +""" import hashlib import sys import os import re import subprocess -from six import PY3 - -# CHANGE THIS AS APPROPRIATE -path_to_sage = os.path.expanduser('~/bin/sage') -# or try to auto-find it: -# path_to_sage = subprocess.check_output(['which', 'sage']).strip() -# or just tell me: -# path_to_sage = '/usr/local/bin/sage' - -if sys.argv[1].endswith('.sagetex.sage'): - src = sys.argv[1][:-13] -else: - src = os.path.splitext(sys.argv[1])[0] - -usepackage = r'usepackage(\[.*\])?{sagetex}' -uses_sagetex = False - -# if it does not use sagetex, obviously running sage is unnecessary -with open(src + '.tex') as texf: - for line in texf: - if line.strip().startswith(r'\usepackage') and re.search(usepackage, line): - uses_sagetex = True - break - -if not uses_sagetex: - print(src + ".tex doesn't seem to use SageTeX, exiting.") - sys.exit(0) - -# if something goes wrong, assume we need to run Sage -run_sage = True -ignore = r"^( _st_.goboom|print\('SageT| ?_st_.current_tex_line)" - -try: - with open(src + '.sagetex.sage', 'r') as sagef: - h = hashlib.md5() - for line in sagef: - if not re.search(ignore, line): - if PY3: +import shutil +import argparse + +def argparser(): + p = argparse.ArgumentParser(description=__doc__.strip()) + p.add_argument('--sage', action='store', default=find_sage(), + help="Location of the Sage executable") + p.add_argument('src', help="Input file name (basename or .sagetex.sage)") + return p + +def find_sage(): + return shutil.which('sage') or 'sage' + +def run(args): + src = args.src + path_to_sage = args.sage + + if src.endswith('.sagetex.sage'): + src = src[:-13] + else: + src = os.path.splitext(src)[0] + + usepackage = r'usepackage(\[.*\])?{sagetex}' + uses_sagetex = False + + # if it does not use sagetex, obviously running sage is unnecessary + with open(src + '.tex') as texf: + for line in texf: + if line.strip().startswith(r'\usepackage') and re.search(usepackage, line): + uses_sagetex = True + break + + if not uses_sagetex: + print(src + ".tex doesn't seem to use SageTeX, exiting.", file=sys.stderr) + sys.exit(1) + + # if something goes wrong, assume we need to run Sage + run_sage = True + ignore = r"^( _st_.goboom|print\('SageT| ?_st_.current_tex_line)" + + try: + with open(src + '.sagetex.sage', 'r') as sagef: + h = hashlib.md5() + for line in sagef: + if not re.search(ignore, line): h.update(bytearray(line,'utf8')) - else: - h.update(bytearray(line)) -except IOError: - print('{0}.sagetex.sage not found, I think you need to typeset {0}.tex first.'.format(src)) - sys.exit(1) - -try: - with open(src + '.sagetex.sout', 'r') as outf: - for line in outf: - m = re.match('%([0-9a-f]+)% md5sum', line) - if m: - print('computed md5:', h.hexdigest()) - print('sagetex.sout md5:', m.group(1)) - if h.hexdigest() == m.group(1): - run_sage = False - break -except IOError: - pass - -if run_sage: - print('Need to run Sage on {0}.'.format(src)) - sys.exit(subprocess.call([path_to_sage, src + '.sagetex.sage'])) -else: - print('Not necessary to run Sage on {0}.'.format(src)) + except IOError: + print('{0}.sagetex.sage not found, I think you need to typeset {0}.tex first.' + ''.format(src), file=sys.stderr) + sys.exit(1) + + try: + with open(src + '.sagetex.sout', 'r') as outf: + for line in outf: + m = re.match('%([0-9a-f]+)% md5sum', line) + if m: + print('computed md5:', h.hexdigest()) + print('sagetex.sout md5:', m.group(1)) + if h.hexdigest() == m.group(1): + run_sage = False + break + except IOError: + pass + + if run_sage: + print('Need to run Sage on {0}.'.format(src)) + sys.exit(subprocess.call([path_to_sage, src + '.sagetex.sage'])) + else: + print('Not necessary to run Sage on {0}.'.format(src)) + +if __name__ == "__main__": + run(argparser().parse_args()) % \end{macrocode} % -% \subsection{makestatic.py} -% \label{sec:makestatic} +% \subsection{\texttt{sagetex-makestatic}} +% \label{sec:sagetex-makestatic} % \iffalse % %<*staticscript> % \fi % -% Now the |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. +% Now the |sagetex-makestatic|: +% % \begin{macrocode} -import sys -import time -import getopt -import os.path -from sagetexparse import DeSageTex - -def usage(): - print("""Usage: %s [-h|--help] [-o|--overwrite] inputfile [outputfile] - +""" Removes SageTeX macros from `inputfile' and replaces them with the Sage-computed results to make a "static" file. You'll need to have run Sage on `inputfile' already. @@ -136,53 +135,57 @@ Sage on `inputfile' already. `outputfile', the results will be written to a file of that name. Specify `-o' or `--overwrite' to overwrite the file if it exists. -See the SageTeX documentation for more details.""" % sys.argv[0]) - -try: - opts, args = getopt.getopt(sys.argv[1:], 'ho', ['help', 'overwrite']) -except getopt.GetoptError as err: - print(str(err)) - usage() - sys.exit(2) - -overwrite = False -for o, a in opts: - if o in ('-h', '--help'): - usage() - sys.exit() - elif o in ('-o', '--overwrite'): - overwrite = True - -if len(args) == 0 or len(args) > 2: - print('Error: wrong number of arguments. Make sure to specify options first.\n') - usage() - sys.exit(2) - -if len(args) == 2 and (os.path.exists(args[1]) and not overwrite): - print('Error: %s exists and overwrite option not specified.' % args[1]) - sys.exit(1) - -src, ext = os.path.splitext(args[0]) +See the SageTeX documentation for more details. +""" +import sys +import time +import os.path +import argparse + +from sagetexparse import DeSageTex + +def argparser(): + p = argparse.ArgumentParser(description=__doc__.strip()) + p.add_argument('inputfile', help="Input file name (basename or .tex)") + p.add_argument('outputfile', nargs='?', default=None, help="Output file name") + p.add_argument('-o', '--overwrite', action="store_true", default=False, + help="Overwrite output file if it exists") + return p + +def run(args): + src, dst, overwrite = args.inputfile, args.outputfile, args.overwrite + + if dst is not None and (os.path.exists(dst) and not overwrite): + print('Error: %s exists and overwrite option not specified.' % dst, + file=sys.stderr) + sys.exit(1) + + src, ext = os.path.splitext(src) % \end{macrocode} % All the real work gets done in the line below. Sorry it's not more % exciting-looking. % \begin{macrocode} -desagetexed = DeSageTex(src) + desagetexed = DeSageTex(src) % \end{macrocode} % 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) \emph{and} because \textsf{Docstrip} needs them (so the % line gets passed into the generated file). It's perfect! % \begin{macrocode} -header = "%% SageTeX commands have been automatically removed from this file and\n%% replaced with plain LaTeX. Processed %s.\n" % time.strftime('%a %d %b %Y %H:%M:%S', time.localtime()) + header = ("%% SageTeX commands have been automatically removed from this file and\n" + "%% replaced with plain LaTeX. Processed %s.\n" + "" % time.strftime('%a %d %b %Y %H:%M:%S', time.localtime())) + + if dst is not None: + dest = open(dst, 'w') + else: + dest = sys.stdout -if len(args) == 2: - dest = open(args[1], 'w') -else: - dest = sys.stdout + dest.write(header) + dest.write(desagetexed.result) -dest.write(header) -dest.write(desagetexed.result) +if __name__ == "__main__": + run(argparser().parse_args()) % \end{macrocode} % % \iffalse @@ -190,20 +193,12 @@ dest.write(desagetexed.result) %<*extractscript> % \fi % -% \subsection{extractsagecode.py} +% \subsection{\texttt{sagetex-extract}} % -% Same idea as |makestatic.py|, except this does basically the opposite +% Same idea as |sagetex-makestatic|, except this does basically the opposite % thing. % \begin{macrocode} -import sys -import time -import getopt -import os.path -from sagetexparse import SageCodeExtractor - -def usage(): - print("""Usage: %s [-h|--help] [-o|--overwrite] inputfile [outputfile] - +""" Extracts Sage code from `inputfile'. `inputfile' can include the .tex extension or not. If you provide @@ -212,47 +207,47 @@ otherwise the result will be printed to stdout. Specify `-o' or `--overwrite' to overwrite the file if it exists. -See the SageTeX documentation for more details.""" % sys.argv[0]) - -try: - opts, args = getopt.getopt(sys.argv[1:], 'ho', ['help', 'overwrite']) -except getopt.GetoptError as err: - print(str(err)) - usage() - sys.exit(2) - -overwrite = False -for o, a in opts: - if o in ('-h', '--help'): - usage() - sys.exit() - elif o in ('-o', '--overwrite'): - overwrite = True - -if len(args) == 0 or len(args) > 2: - print('Error: wrong number of arguments. Make sure to specify options first.\n') - usage() - sys.exit(2) - -if len(args) == 2 and (os.path.exists(args[1]) and not overwrite): - print('Error: %s exists and overwrite option not specified.' % args[1]) - sys.exit(1) - -src, ext = os.path.splitext(args[0]) -sagecode = SageCodeExtractor(src) -header = """\ -# This file contains Sage code extracted from %s%s. -# Processed %s. - -""" % (src, ext, time.strftime('%a %d %b %Y %H:%M:%S', time.localtime())) - -if len(args) == 2: - dest = open(args[1], 'w') -else: - dest = sys.stdout - -dest.write(header) -dest.write(sagecode.result) +See the SageTeX documentation for more details. +""" +import sys +import time +import os.path +import argparse + +from sagetexparse import SageCodeExtractor + +def argparser(): + p = argparse.ArgumentParser(description=__doc__.strip()) + p.add_argument('inputfile', help="Input file name (basename or .tex)") + p.add_argument('outputfile', nargs='?', default=None, help="Output file name") + p.add_argument('-o', '--overwrite', action="store_true", default=False, + help="Overwrite output file if it exists") + return p + +def run(args): + src, dst, overwrite = args.inputfile, args.outputfile, args.overwrite + + if dst is not None and (os.path.exists(dst) and not overwrite): + print('Error: %s exists and overwrite option not specified.' % dst, + file=sys.stderr) + sys.exit(1) + + src, ext = os.path.splitext(src) + sagecode = SageCodeExtractor(src) + header = ("# This file contains Sage code extracted from %s%s.\n" + "# Processed %s.\n" + "" % (src, ext, time.strftime('%a %d %b %Y %H:%M:%S', time.localtime()))) + + if dst is not None: + dest = open(dst, 'w') + else: + dest = sys.stdout + + dest.write(header) + dest.write(sagecode.result) + +if __name__ == "__main__": + run(argparser().parse_args()) % \end{macrocode} % % \iffalse @@ -496,7 +491,7 @@ class SageCodeExtractor(): def plotout(self, s, l, t): self.result += '# \\sageplot{} from line %s:\n' % lineno(l, s) - if t.format is not '': + if t.format != '': self.result += '# format: %s' % t.format[0][1:-1] + '\n' self.result += t.code[1:-1] + '\n\n' diff --git a/setup.py b/setup.py index 28e2712..1fb3513 100644 --- a/setup.py +++ b/setup.py @@ -11,20 +11,17 @@ maintainer_email='sage-devel@googlegroups.com', url='https://github.com/sagemath/sagetex', license='GPLv2+', - py_modules=['sagetex'], + py_modules=['sagetex', 'sagetexparse'], + scripts=['sagetex-run', 'sagetex-extract', 'sagetex-makestatic', 'sagetex-remote'], + install_requires=['pyparsing'], data_files = [('share/texmf/tex/latex/sagetex', ['example.tex', 'CONTRIBUTORS', - 'extractsagecode.py', - 'run-sagetex-if-necessary.py', - 'makestatic.py', 'scripts.dtx', 'remote-sagetex.dtx', - 'remote-sagetex.py', 'py-and-sty.dtx', 'sagetex.dtx', 'sagetex.ins', - 'sagetexparse.py', 'sagetex.sty']), ('share/doc/sagetex', [ 'example.tex', From 7c120bc1f0b3972f7f33c246a4d9cae7237eb170 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 13 May 2021 20:02:40 +0300 Subject: [PATCH 2/5] Make scripts work when Latex output is not in the same directory as .tex --- scripts.dtx | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/scripts.dtx b/scripts.dtx index 07096ee..fd1b8d4 100644 --- a/scripts.dtx +++ b/scripts.dtx @@ -64,15 +64,24 @@ def run(args): else: src = os.path.splitext(src)[0] + # Ensure results are output in the same directory as the source files + os.chdir(os.path.dirname(src)) + src = os.path.basename(src) + usepackage = r'usepackage(\[.*\])?{sagetex}' uses_sagetex = False - # if it does not use sagetex, obviously running sage is unnecessary - with open(src + '.tex') as texf: - for line in texf: - if line.strip().startswith(r'\usepackage') and re.search(usepackage, line): - uses_sagetex = True - break + # If it does not use sagetex, obviously running sage is unnecessary. + if os.path.isfile(src + '.tex'): + with open(src + '.tex') as texf: + for line in texf: + if line.strip().startswith(r'\usepackage') and re.search(usepackage, line): + uses_sagetex = True + break + else: + # The .tex file might not exist if LaTeX output was put to a different + # directory, so in that case just assume we need to build. + uses_sagetex = True if not uses_sagetex: print(src + ".tex doesn't seem to use SageTeX, exiting.", file=sys.stderr) @@ -150,6 +159,8 @@ def argparser(): p.add_argument('outputfile', nargs='?', default=None, help="Output file name") p.add_argument('-o', '--overwrite', action="store_true", default=False, help="Overwrite output file if it exists") + p.add_argument('-s', '--sout', action="store", default=None, + help="Location of the .sagetex.sout file") return p def run(args): @@ -161,11 +172,13 @@ def run(args): sys.exit(1) src, ext = os.path.splitext(src) + texfn = src + '.tex' + soutfn = args.sout if args.sout is not None else src + '.sagetex.sout' % \end{macrocode} % All the real work gets done in the line below. Sorry it's not more % exciting-looking. % \begin{macrocode} - desagetexed = DeSageTex(src) + desagetexed = DeSageTex(texfn, soutfn) % \end{macrocode} % 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 @@ -233,7 +246,7 @@ def run(args): sys.exit(1) src, ext = os.path.splitext(src) - sagecode = SageCodeExtractor(src) + sagecode = SageCodeExtractor(src + '.tex') header = ("# This file contains Sage code extracted from %s%s.\n" "# Processed %s.\n" "" % (src, ext, time.strftime('%a %d %b %Y %H:%M:%S', time.localtime()))) @@ -266,6 +279,7 @@ if __name__ == "__main__": % over the screen. % \begin{macrocode} import sys +import os from pyparsing import * % \end{macrocode} % First, we define this very helpful parser: it finds the matching @@ -351,11 +365,11 @@ class SoutParser(): % that the provided |fn| is just a basename. % \begin{macrocode} class DeSageTex(): - def __init__(self, fn): + def __init__(self, texfn, soutfn): self.sagen = 0 self.plotn = 0 - self.fn = fn - self.sout = SoutParser(fn + '.sagetex.sout') + self.fn = os.path.basename(texfn) + self.sout = SoutParser(soutfn) % \end{macrocode} % 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. @@ -417,7 +431,7 @@ class DeSageTex(): % |transformString| on it, since that will just pick out the interesting % bits and munge them according to the above definitions. % \begin{macrocode} - str = ''.join(open(fn + '.tex', 'r').readlines()) + str = ''.join(open(texfn, 'r').readlines()) self.result = doit.transformString(str) % \end{macrocode} % That's the end of the class constructor, and it's all we need to do @@ -451,7 +465,7 @@ class DeSageTex(): % Sage. % \begin{macrocode} class SageCodeExtractor(): - def __init__(self, fn): + def __init__(self, texfn): smacro = sagemacroparser smacro.setParseAction(self.macroout) @@ -480,7 +494,7 @@ class SageCodeExtractor(): doit = smacro | splot | senv | spause | sunpause - str = ''.join(open(fn + '.tex', 'r').readlines()) + str = ''.join(open(texfn, 'r').readlines()) self.result = '' doit.transformString(str) From 5a855de150b9fc2819bca46d94fc6ac23a87f994 Mon Sep 17 00:00:00 2001 From: Vincent Ledda Date: Mon, 5 Apr 2021 12:46:41 +0200 Subject: [PATCH 3/5] Fix makestatic for recent version of pyparsing It's working with pyparsing 2.4.7 Add sagestr support. Sageexample environment is still not supported (cherry-picked from pull request #55 by @vincek59) --- scripts.dtx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts.dtx b/scripts.dtx index fd1b8d4..e6b728e 100644 --- a/scripts.dtx +++ b/scripts.dtx @@ -290,8 +290,7 @@ from pyparsing import * % \begin{macrocode} def skipToMatching(opener, closer): nest = nestedExpr(opener, closer) - nest.setParseAction(lambda l, s, t: l[s:getTokensEndLoc()]) - return nest + return originalTextFor(nest) curlybrackets = skipToMatching('{', '}') squarebrackets = skipToMatching('[', ']') @@ -299,6 +298,7 @@ squarebrackets = skipToMatching('[', ']') % Next, parser for |\sage|, |\sageplot|, and pause/unpause calls: % \begin{macrocode} sagemacroparser = r'\sage' + curlybrackets('code') +sagestrmacroparser = r'\sagestr' + curlybrackets('code') sageplotparser = (r'\sageplot' + Optional(squarebrackets)('opts') + Optional(squarebrackets)('format') @@ -374,8 +374,10 @@ class DeSageTex(): % 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. % \begin{macrocode} + strmacro = sagestrmacroparser smacro = sagemacroparser smacro.setParseAction(self.sage) + strmacro.setParseAction(self.sage) % \end{macrocode} % Parse the |\usepackage{sagetex}| line. Right now we don't support % comma-separated lists of packages. @@ -419,7 +421,7 @@ class DeSageTex(): % looks for any one of the above bits, while ignoring anything that % should be ignored. % \begin{macrocode} - doit = smacro | senv | ssilent | usepackage | splot | stexindent + doit = smacro | senv | ssilent | usepackage | splot | stexindent |strmacro doit.ignore('%' + restOfLine) doit.ignore(r'\begin{verbatim}' + SkipTo(r'\end{verbatim}')) doit.ignore(r'\begin{comment}' + SkipTo(r'\end{comment}')) From afaac840a43c84d32fcdf1ca92b29563f0d5fd57 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 13 May 2021 20:40:40 +0300 Subject: [PATCH 4/5] Fix extractsagecode env blocks for new pyparsing + ignore comments --- scripts.dtx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts.dtx b/scripts.dtx index e6b728e..e514c4d 100644 --- a/scripts.dtx +++ b/scripts.dtx @@ -495,6 +495,7 @@ class SageCodeExtractor(): sunpause.setParseAction(self.unpause) doit = smacro | splot | senv | spause | sunpause + doit.ignore('%' + restOfLine) str = ''.join(open(texfn, 'r').readlines()) self.result = '' @@ -514,7 +515,7 @@ class SageCodeExtractor(): def envout(self, s, l, t): self.result += '# %s environment from line %s:' % (t.env, lineno(l, s)) - self.result += t.code[0] + '\n' + self.result += ''.join(t.code) + '\n' def pause(self, s, l, t): self.result += ('# SageTeX (probably) paused on input line %s.\n\n' % From 631e43d540491d77198c9b9fbcb1020bdd716645 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 13 May 2021 20:49:30 +0300 Subject: [PATCH 5/5] extractsagecode: add option for extracting only environments + dedent Fix pyparsing compatibility, and dedent code appropriately. Also, use a different comment prefix for comments output by the extraction tool. --- scripts.dtx | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/scripts.dtx b/scripts.dtx index e514c4d..988f973 100644 --- a/scripts.dtx +++ b/scripts.dtx @@ -235,6 +235,8 @@ def argparser(): p.add_argument('outputfile', nargs='?', default=None, help="Output file name") p.add_argument('-o', '--overwrite', action="store_true", default=False, help="Overwrite output file if it exists") + p.add_argument('--no-inline', action="store_true", default=False, + help="Extract code only from Sage environments") return p def run(args): @@ -246,9 +248,9 @@ def run(args): sys.exit(1) src, ext = os.path.splitext(src) - sagecode = SageCodeExtractor(src + '.tex') - header = ("# This file contains Sage code extracted from %s%s.\n" - "# Processed %s.\n" + sagecode = SageCodeExtractor(src + '.tex', inline=not args.no_inline) + header = ("#> This file contains Sage code extracted from %s%s.\n" + "#> Processed %s.\n" "" % (src, ext, time.strftime('%a %d %b %Y %H:%M:%S', time.localtime()))) if dst is not None: @@ -280,6 +282,7 @@ if __name__ == "__main__": % \begin{macrocode} import sys import os +import textwrap from pyparsing import * % \end{macrocode} % First, we define this very helpful parser: it finds the matching @@ -467,7 +470,7 @@ class DeSageTex(): % Sage. % \begin{macrocode} class SageCodeExtractor(): - def __init__(self, texfn): + def __init__(self, texfn, inline=True): smacro = sagemacroparser smacro.setParseAction(self.macroout) @@ -494,7 +497,10 @@ class SageCodeExtractor(): sunpause = sagetexunpause sunpause.setParseAction(self.unpause) - doit = smacro | splot | senv | spause | sunpause + if inline: + doit = smacro | splot | senv | spause | sunpause + else: + doit = senv | spause | sunpause doit.ignore('%' + restOfLine) str = ''.join(open(texfn, 'r').readlines()) @@ -503,26 +509,26 @@ class SageCodeExtractor(): doit.transformString(str) def macroout(self, s, l, t): - self.result += '# \\sage{} from line %s\n' % lineno(l, s) - self.result += t.code[1:-1] + '\n\n' + self.result += '#> \\sage{} from line %s\n' % lineno(l, s) + self.result += textwrap.dedent(t.code[1:-1]) + '\n\n' def plotout(self, s, l, t): - self.result += '# \\sageplot{} from line %s:\n' % lineno(l, s) + self.result += '#> \\sageplot{} from line %s:\n' % lineno(l, s) if t.format != '': - self.result += '# format: %s' % t.format[0][1:-1] + '\n' - self.result += t.code[1:-1] + '\n\n' + self.result += '#> format: %s' % t.format[0][1:-1] + '\n' + self.result += textwrap.dedent(t.code[1:-1]) + '\n\n' def envout(self, s, l, t): - self.result += '# %s environment from line %s:' % (t.env, + self.result += '#> %s environment from line %s:' % (t.env, lineno(l, s)) - self.result += ''.join(t.code) + '\n' + self.result += textwrap.dedent(''.join(t.code)) + '\n' def pause(self, s, l, t): - self.result += ('# SageTeX (probably) paused on input line %s.\n\n' % + self.result += ('#> SageTeX (probably) paused on input line %s.\n\n' % (lineno(l, s))) def unpause(self, s, l, t): - self.result += ('# SageTeX (probably) unpaused on input line %s.\n\n' % + self.result += ('#> SageTeX (probably) unpaused on input line %s.\n\n' % (lineno(l, s))) % \end{macrocode} % \end{macro}