diff options
70 files changed, 15626 insertions, 2 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.am b/Build/source/texk/texlive/linked_scripts/Makefile.am index 91fb4168680..eb793bf3d03 100644 --- a/Build/source/texk/texlive/linked_scripts/Makefile.am +++ b/Build/source/texk/texlive/linked_scripts/Makefile.am @@ -136,6 +136,8 @@ texmf_other_scripts = \ pst2pdf/pst2pdf.pl \ ptex2pdf/ptex2pdf.lua \ purifyeps/purifyeps \ + pythontex/pythontex.py \ + pythontex/depythontex.py \ splitindex/splitindex.pl \ sty2dtx/sty2dtx.pl \ svn-multi/svn-multi.pl \ diff --git a/Build/source/texk/texlive/linked_scripts/Makefile.in b/Build/source/texk/texlive/linked_scripts/Makefile.in index f8d3a8c9631..a5adb819de8 100644 --- a/Build/source/texk/texlive/linked_scripts/Makefile.in +++ b/Build/source/texk/texlive/linked_scripts/Makefile.in @@ -341,6 +341,8 @@ texmf_other_scripts = \ pst2pdf/pst2pdf.pl \ ptex2pdf/ptex2pdf.lua \ purifyeps/purifyeps \ + pythontex/pythontex.py \ + pythontex/depythontex.py \ splitindex/splitindex.pl \ sty2dtx/sty2dtx.pl \ svn-multi/svn-multi.pl \ diff --git a/Build/source/texk/texlive/linked_scripts/pythontex/depythontex.py b/Build/source/texk/texlive/linked_scripts/pythontex/depythontex.py new file mode 100755 index 00000000000..013e268241f --- /dev/null +++ b/Build/source/texk/texlive/linked_scripts/pythontex/depythontex.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +''' +This is the PythonTeX wrapper script. It automatically detects the version +of Python, and then imports the correct code from depythontex2.py or +depythontex3.py. + +Copyright (c) 2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + +import sys +if sys.version_info[0] == 2: + import depythontex2 as depythontex +elif sys.version_info[0] == 3: + import depythontex3 as depythontex diff --git a/Build/source/texk/texlive/linked_scripts/pythontex/pythontex.py b/Build/source/texk/texlive/linked_scripts/pythontex/pythontex.py new file mode 100755 index 00000000000..1be1f6d70ea --- /dev/null +++ b/Build/source/texk/texlive/linked_scripts/pythontex/pythontex.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +This is the PythonTeX wrapper script. It automatically detects the version +of Python, and then imports the correct code from pythontex2.py or +pythontex3.py. + +Licensed under the BSD 3-Clause License: + +Copyright (c) 2012-2013, Geoffrey M. Poore + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' + + +import sys +if sys.version_info.major == 2: + if sys.version_info.minor >= 7: + import pythontex2 as pythontex + else: + sys.exit('PythonTeX require Python 2.7; you are using 2.{0}'.format(sys.version_info.minor)) +elif sys.version_info.major == 3: + if sys.version_info.minor >= 2: + import pythontex3 as pythontex + else: + sys.exit('PythonTeX require Python 3.2+; you are using 3.{0}'.format(sys.version_info.minor)) + +# The "if" statement is needed for multiprocessing under Windows; see the +# multiprocessing documentation. +if __name__ == '__main__': + pythontex.main() diff --git a/Master/bin/alpha-linux/depythontex b/Master/bin/alpha-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/alpha-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/alpha-linux/pythontex b/Master/bin/alpha-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/alpha-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/amd64-freebsd/depythontex b/Master/bin/amd64-freebsd/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/amd64-freebsd/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/amd64-freebsd/pythontex b/Master/bin/amd64-freebsd/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/amd64-freebsd/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/amd64-kfreebsd/depythontex b/Master/bin/amd64-kfreebsd/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/amd64-kfreebsd/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/amd64-kfreebsd/pythontex b/Master/bin/amd64-kfreebsd/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/amd64-kfreebsd/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/amd64-netbsd/depythontex b/Master/bin/amd64-netbsd/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/amd64-netbsd/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/amd64-netbsd/pythontex b/Master/bin/amd64-netbsd/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/amd64-netbsd/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/armel-linux/depythontex b/Master/bin/armel-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/armel-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/armel-linux/pythontex b/Master/bin/armel-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/armel-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/armhf-linux/depythontex b/Master/bin/armhf-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/armhf-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/armhf-linux/pythontex b/Master/bin/armhf-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/armhf-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-cygwin/depythontex b/Master/bin/i386-cygwin/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/i386-cygwin/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-cygwin/pythontex b/Master/bin/i386-cygwin/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/i386-cygwin/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-freebsd/depythontex b/Master/bin/i386-freebsd/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/i386-freebsd/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-freebsd/pythontex b/Master/bin/i386-freebsd/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/i386-freebsd/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-kfreebsd/depythontex b/Master/bin/i386-kfreebsd/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/i386-kfreebsd/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-kfreebsd/pythontex b/Master/bin/i386-kfreebsd/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/i386-kfreebsd/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-linux/depythontex b/Master/bin/i386-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/i386-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-linux/pythontex b/Master/bin/i386-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/i386-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-netbsd/depythontex b/Master/bin/i386-netbsd/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/i386-netbsd/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-netbsd/pythontex b/Master/bin/i386-netbsd/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/i386-netbsd/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-solaris/depythontex b/Master/bin/i386-solaris/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/i386-solaris/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/i386-solaris/pythontex b/Master/bin/i386-solaris/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/i386-solaris/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/mips-irix/depythontex b/Master/bin/mips-irix/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/mips-irix/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/mips-irix/pythontex b/Master/bin/mips-irix/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/mips-irix/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/mipsel-linux/depythontex b/Master/bin/mipsel-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/mipsel-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/mipsel-linux/pythontex b/Master/bin/mipsel-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/mipsel-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/powerpc-linux/depythontex b/Master/bin/powerpc-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/powerpc-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/powerpc-linux/pythontex b/Master/bin/powerpc-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/powerpc-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/sparc-solaris/depythontex b/Master/bin/sparc-solaris/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/sparc-solaris/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/sparc-solaris/pythontex b/Master/bin/sparc-solaris/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/sparc-solaris/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/universal-darwin/depythontex b/Master/bin/universal-darwin/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/universal-darwin/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/universal-darwin/pythontex b/Master/bin/universal-darwin/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/universal-darwin/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/win32/depythontex.exe b/Master/bin/win32/depythontex.exe Binary files differnew file mode 100755 index 00000000000..5777d90a17a --- /dev/null +++ b/Master/bin/win32/depythontex.exe diff --git a/Master/bin/win32/pythontex.exe b/Master/bin/win32/pythontex.exe Binary files differnew file mode 100755 index 00000000000..5777d90a17a --- /dev/null +++ b/Master/bin/win32/pythontex.exe diff --git a/Master/bin/x86_64-darwin/depythontex b/Master/bin/x86_64-darwin/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/x86_64-darwin/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/x86_64-darwin/pythontex b/Master/bin/x86_64-darwin/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/x86_64-darwin/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/x86_64-linux/depythontex b/Master/bin/x86_64-linux/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/x86_64-linux/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/x86_64-linux/pythontex b/Master/bin/x86_64-linux/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/x86_64-linux/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/bin/x86_64-solaris/depythontex b/Master/bin/x86_64-solaris/depythontex new file mode 120000 index 00000000000..2066614112a --- /dev/null +++ b/Master/bin/x86_64-solaris/depythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/depythontex.py
\ No newline at end of file diff --git a/Master/bin/x86_64-solaris/pythontex b/Master/bin/x86_64-solaris/pythontex new file mode 120000 index 00000000000..95cdfd9b0db --- /dev/null +++ b/Master/bin/x86_64-solaris/pythontex @@ -0,0 +1 @@ +../../texmf-dist/scripts/pythontex/pythontex.py
\ No newline at end of file diff --git a/Master/texmf-dist/doc/latex/pythontex/README b/Master/texmf-dist/doc/latex/pythontex/README new file mode 100644 index 00000000000..3c7247e9dcf --- /dev/null +++ b/Master/texmf-dist/doc/latex/pythontex/README @@ -0,0 +1,68 @@ +=================================================================== + The PythonTeX Package +=================================================================== + +:Author: Geoffrey Poore +:Version: 0.12 (2013/08/26) +:License: LPPL v1.3 or later (LaTeX code) and BSD 3-Clause (Python code) +:Development: https://github.com/gpoore/pythontex +:Requirements: Python 2.7 or 3.2+; Pygments + + +The PythonTeX package allows you to enter Python code within a LaTeX +document, execute the code, and access its output in the original document. +Since v0.12, it also supports the Ruby and Julia languages. + +PythonTeX provides fast, user-friendly access to Python from within LaTeX. +Python code is only executed when it has been modified, or when it meets +user-specified criteria. Code may be divided into user-defined sessions, +which automatically run in parallel. Errors and warnings are synchronized +with the LaTeX document, so that they refer to the document's line numbers. +External dependencies can be tracked, so that code is re-executed when the +data it depends on is modified. PythonTeX also provides syntax highlighting +for code in LaTeX documents via the Pygments syntax highlighter. + +PythonTeX includes a "depythontex" utility that creates a copy of a document +in which all Python code has been replaced by its output. This can be +convenient for journal submissions, sharing documents, and converting to +other formats. + +Consult pythontex_quickstart.pdf and pythontex_gallery.pdf to get +started and for examples of what PythonTeX can do. + +Compiling a PythonTeX document involves three steps: + +1. Run latex (all Python code is saved to an auxiliary file) +2. Run pythontex.py (all code is executed) +3. Run latex again (Python output is inputted) + +See the main documentation in pythontex.pdf for detailed installation +instructions. An installation script is provided for TeX Live. Files +should be installed in the directories indicated below. In summary, +all files must be installed, and symlinks or launching wrappers should be +created for pythontex.py and depythontex.py. + +* /doc/latex/pythontex/ + + - pythontex.pdf + - README + - pythontex_quickstart.tex + - pythontex_quickstart.pdf + - pythontex_gallery.tex + - pythontex_gallery.pdf + +* /scripts/pythontex/ + + - pythontex.py, pythontex2.py, and pythontex3.py + - pythontex_engines.py + - pythontex_utils.py + - depythontex.py, depythontex2.py, and depythontex3.py + +* /source/latex/pythontex/ + + - pythontex.dtx + - pythontex.ins + +* /tex/latex/pythontex/ + + - pythontex.sty diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf b/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf Binary files differnew file mode 100644 index 00000000000..4d3c8661c6b --- /dev/null +++ b/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf Binary files differnew file mode 100644 index 00000000000..66ba25e6e4f --- /dev/null +++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex new file mode 100644 index 00000000000..29feb22c309 --- /dev/null +++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex @@ -0,0 +1,287 @@ +\documentclass[11pt]{article} + +% Engine-specific settings +% Detect pdftex/xetex/luatex, and load appropriate font packages. +% This is inspired by the approach in the iftex package. +% pdftex: +\expandafter\ifx\csname pdfmatch\endcsname\relax +\else + \usepackage[T1]{fontenc} + \usepackage[utf8]{inputenc} +\fi +% xetex: +\expandafter\ifx\csname XeTeXinterchartoks\endcsname\relax +\else + \usepackage{fontspec} + \defaultfontfeatures{Ligatures=TeX} +\fi +% luatex: +\expandafter\ifx\csname directlua\endcsname\relax +\else + \usepackage{fontspec} +\fi +% End engine-specific settings + +\usepackage{amsmath,amssymb} +\usepackage{fullpage} +\usepackage{graphicx} +\usepackage[svgnames]{xcolor} +\usepackage{url} +\urlstyle{same} + +\usepackage[makestderr]{pythontex} +\restartpythontexsession{\thesection} + + +\usepackage[framemethod=TikZ]{mdframed} + +\newcommand{\pytex}{Python\TeX} +\renewcommand*{\thefootnote}{\fnsymbol{footnote}} + + +\title{\pytex\ Gallery} +\author{Geoffrey M.\ Poore} + + +\begin{document} + + +\maketitle + + +\begin{abstract} +\pytex\ allows you to run Python code from within \LaTeX\ documents and automatically include the output. This document serves as an example of what is possible with \pytex.\footnote{Since \pytex\ runs Python code (and potentially other code) on your computer, documents using \pytex\ have a greater potential for security risks than do standard \LaTeX\ documents. You should only compile \pytex\ documents from sources you trust.} +\end{abstract} + + + +\section{General Python interaction} + +We can typeset code that is passed to Python, and bring back the results. + +This can be simple. For example, \pyb{print('Python says hi!')} returns the following: +\begin{quote} +\printpythontex +\end{quote} + +Or we could access the printed content verbatim (it might contain special characters): +\begin{quote} +\printpythontex[verb] +\end{quote} + +Python interaction can also be more complex. \pyb{print(str(2**2**2) + r'\endinput')} returns \printpythontex. In this case, the printed results include \LaTeX\ code, which is correctly interpreted by \LaTeX\ to ensure that there is not an extra space after the 16. Printed output is saved to a file and brought back in via \verb|\input|, and the \verb|\endinput| command stops input immediately, before \LaTeX\ gets to the end of the line and inserts a space character there, after the 16. + +Printing works, but as the last example demonstrates, you have to be careful about spacing if you have text immediately after the printed content. In that case, it's usually best to assemble text within a \pytex\ environment and store the text in a variable. Then you can bring in the text later, using the \pygment{latex}{\py} command. The \pygment{latex}{\py} command brings in a string representation of its argument. First we create the text. +\begin{pyblock} +mytext = '$1 + 1 = {0}$'.format(1 + 1) +\end{pyblock} +Then we bring it in: \py{mytext}. The \pygment{latex}{\py} command can even bring in verbatim content, as long as you are using the pdfTeX or XeTeX engines. (Unfortunately, verbatim does not work with LuaTeX, due to a bug.\footnote{\url{http://tracker.luatex.org/view.php?id=733}}) + +We don't have to typeset the code we're executing. It can be hidden.\pyc{mystring=r'\textbf{This is a message from Python}'} And then we can access it later: \py{mystring}. + + +\section{Pygments highlighting} + +\pytex\ supports syntax highlighting via Pygments. Any language supported by Pygments can be highlighted. Unicode is supported. Consider this snippet copied and pasted from a Python 3 interactive session. (Using random strings of Unicode for variable names is probably not a good idea, but \pytex\ will happily highlight it for you.) + +\begin{pygments}{pycon} +>>> âæéöø = 123 +>>> ßçñðŠ= 456 +>>> âæéöø + ßçñðŠ+579 +\end{pygments} + +There is also a Pygments command for inline use: \pygment{latex}{\pygment}. + + +\section{Python console environment} + +\pytex\ includes an environment that emulates a Python interactive session. Commands are entered within the environment, each line is treated as input to an interactive session, and the result is typeset. + +\begin{pyconsole}[][frame=single] +x = 123 +y = 345 +z = x + y +z +def f(expr): + return(expr**4) + +f(x) +print('Python says hi from the console!') +\end{pyconsole} + +It is possible to refer to the values of console variables later on in inline contexts, using the \pygment{latex}{\pycon} command. For example, the value of $z$ was \pycon{z}. + + +\section{Basic SymPy interaction} + +\pytex\ allows us to perform algebraic manipulations with SymPy and then properly typeset the results. + +We create three variables, and define $z$ in terms of the other two. + +\begin{sympyblock} +var('x, y, z') +z = x + y +\end{sympyblock} + +Now we can access what $z$ is equal to: + +\[z=\sympy{z}\] + +Many things are possible, including some very nice calculus. + +\begin{sympyblock} +f = x**3 + cos(x)**5 +g = Integral(f, x) +\end{sympyblock} + +\[\sympy{g}=\sympy{g.doit()}\] + +It's easy to use arbitrary symbols in equations. + +\begin{sympyblock} +phi = Symbol(r'\phi') +h = Integral(exp(-phi**2), (phi, 0, oo)) +\end{sympyblock} + +\[\sympy{h}=\sympy{h.doit()}\] + + + +\section{Plots with matplotlib} + +We can create plots with matplotlib, perfectly matching the plot fonts with the document fonts. No more searching for the code that created a figure! + +You may want to use matplotlib's PGF backend when creating plots. + +\begin{pylabblock} +rc('text', usetex=True) +rc('font', family='serif') +rc('font', size=10.0) +rc('legend', fontsize=10.0) +rc('font', weight='normal') +x = linspace(0, 10) +figure(figsize=(4, 2.5)) +plot(x, sin(x), label='$\sin(x)$') +xlabel(r'$x\mathrm{-axis}$') +ylabel(r'$y\mathrm{-axis}$') +legend(loc='lower right') +savefig('myplot.pdf', bbox_inches='tight') +\end{pylabblock} + +\begin{center} +\includegraphics{myplot.pdf} +\end{center} + + +\section{Basic pylab interaction} + +\begin{pylabblock} +from scipy.integrate import quad +myintegral = quad(lambda x: e**-x**2, 0, inf)[0] +\end{pylabblock} + +\[ \int_0^\infty e^{-x^2}\,dx = \pylab{myintegral} \] + + +\section{An automated derivative and integral table} + +\pytex\ allows some amazing document automation, such as this derivative and integral table. Try typing that by hand, fast! + +\begin{sympyblock}[][numbers=left,frame=single,framesep=5mm,label=An Automated Derivative and Integral Table] +from re import sub + +var('x') + +# Create a list of functions to include in the table +funcs = ['sin(x)', 'cos(x)', 'tan(x)', + 'sin(x)**2', 'cos(x)**2', 'tan(x)**2', + 'asin(x)', 'acos(x)', 'atan(x)', + 'sinh(x)', 'cosh(x)', 'tanh(x)'] + +print(r'\begin{align*}') + +for func in funcs: + # Put in some vertical space when switching to arc and hyperbolic funcs + if func == 'asin(x)' or func == 'sinh(x)': + print(r'&\\') + myderiv = 'Derivative(' + func + ', x)' + myint = 'Integral(' + func + ', x)' + print(latex(eval(myderiv)) + '&=' + + latex(eval(myderiv + '.doit()')) + r'\quad & \quad') + print(latex(eval(myint)) + '&=' + + latex(eval(myint+'.doit()')) + r'\\') +print(r'\end{align*}') +\end{sympyblock} + +\printpythontex + + +\section{Step-by-step solutions} + +Using SymPy, it is possible to typeset step-by-step solutions. In this particular case, we also use the \verb|mdframed| package to place a colored background behind our code. + +\begin{mdframed}[linecolor=Green,innerrightmargin=30pt,innerleftmargin=30pt,leftmargin=-30pt,rightmargin=-30pt,backgroundcolor=Black!5,skipabove=10pt,skipbelow=10pt,roundcorner=5pt,frametitle={Step-by-Step Integral Evaluation},frametitlealignment=\center,splitbottomskip=6pt,splittopskip=12pt] +\begin{sympyblock}[][numbers=left] +x, y, z = symbols('x,y,z') +f = Symbol('f(x,y,z)') + +# Define limits of integration +x_llim = 0 +x_ulim = 2 +y_llim = 0 +y_ulim = 3 +z_llim = 0 +z_ulim = 4 + +print(r'\begin{align*}') + +# Notice how I define f as a symbol, then later as an actual function +left = Integral(f, (x, x_llim, x_ulim), (y, y_llim, y_ulim), (z, z_llim, z_ulim)) +f = x*y + y*sin(z) + cos(x+y) +right = Integral(f, (x, x_llim, x_ulim), (y, y_llim, y_ulim), (z, z_llim, z_ulim)) +print(latex(left) + '&=' + latex(right) + r'\\') + +# For each step, I move limits from an outer integral to an inner, evaluated +# integral until the outer integral is no longer needed +right = Integral(Integral(f, (z, z_llim, z_ulim)).doit(), (x, x_llim, x_ulim), + (y, y_llim, y_ulim)) +print('&=' + latex(right) + r'\\') + +right = Integral(Integral(f, (z, z_llim, z_ulim), (y, y_llim, y_ulim)).doit(), + (x, x_llim, x_ulim)) +print('&=' + latex(right) + r'\\') + +right = Integral(f, (z, z_llim, z_ulim), (y, y_llim, y_ulim), + (x, x_llim, x_ulim)).doit() +print('&=' + latex(right) + r'\\') + +print('&=' + latex(N(right)) + r'\\') + +print(r'\end{align*}') +\end{sympyblock} +\end{mdframed} + +\printpythontex + + +\section{Including stderr} + +\pytex\ allows code to be typset next to the stderr it produces. This requires the package option \verb|makestderr|. + +\begin{pyblock}[errorsession][numbers=left] +x = 123 +y = 345 +z = x + y + +\end{pyblock} + +This code causes a syntax error: + +\stderrpythontex[verbatim][frame=single] + +The package option \verb|stderrfilename| allows the file name that appears in the error message to be customized. + + + + +\end{document} diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf Binary files differnew file mode 100644 index 00000000000..cacc158dce4 --- /dev/null +++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex new file mode 100644 index 00000000000..41d4f43b33b --- /dev/null +++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex @@ -0,0 +1,93 @@ +\documentclass[twocolumn]{article} + +\usepackage[margin=1in]{geometry} +\usepackage{microtype} + +\usepackage{pythontex} + +\usepackage[svgnames]{xcolor} + +\usepackage{nopageno} + +\usepackage{hyperref} +\hypersetup{colorlinks=true,urlcolor=Green,linkcolor=blue} + +\newcommand{\pytex}{Python\TeX} +\renewcommand*{\thefootnote}{\fnsymbol{footnote}} + +\title{\vspace{-0.6in} \pytex\ Quickstart \\ {\normalsize \href{https://github.com/gpoore/pythontex}{github.com/gpoore/pythontex}}} +\author{} +\date{} + + +\begin{document} + + +\maketitle + + + + +\section*{Compiling} + +Compiling a document that uses \pytex\ involves three steps: run latex, run \texttt{pythontex.py}, and finally run latex again. You may wish to create a symlink or launching wrapper for \texttt{pythontex.py}, if one was not created during installation. \pytex\ is compatible with the pdfLaTeX, XeLaTeX, and LuaLaTeX engines. There are minor engine-specific differences. + + +\section*{Commands} + +\pygment{latex}{\py} returns a string representation of its argument. For example, \pygment{latex}{\py{2 + 4**2}} produces \py{2 + 4**2}, and \pygment{latex}{\py{'ABC'.lower()}} produces \py{'ABC'.lower()}. \pygment{latex}{\py}'s argument can be delimited by curly braces, or by a matched pair of other characters (just like \pygment{latex}{\verb}). + +\pygment{latex}{\pyc} executes code. By default, anything that is printed is automatically included in the document (see \texttt{autoprint}/\texttt{autostdout} in the main documentation). For example, \pygment{latex}{\pyc{var = 2}} \pyc{var = 2} creates a variable, and then its value may be accessed later via \pygment{latex}{\py{var}}: \py{var}. + +\pygment{latex}{\pyb} executes and typesets code. For example, \pygment{latex}{\pyb{var = 2}} typesets \pyb{var = 2} in addition to creating the variable. If anything is printed, it is not automatically included, but can be accessed via \pygment{latex}{\printpythontex} and \pygment{latex}{\stdoutpythontex}. + +\pygment{latex}{\pyv} only typesets code. For example, \pygment{latex}{\pyv{var = 2}} produces \pyv{var = 2}. + + +\section*{Environments} + +There are \texttt{pycode}, \texttt{pyblock}, and \texttt{pyverbatim} environments, which are the environment equivalents of \pygment{latex}{\pyc}, \pygment{latex}{\pyb}, and \pygment{latex}{\pyv}. For example, +\begin{pygments}{text} +\begin{pycode} +print(r'\begin{center}') +print(r'\textit{A message from Python!}') +print(r'\end{center}') +\end{pycode} +\end{pygments} +produces +\begin{pycode} +print(r'\begin{center}') +print(r'\textit{A message from Python!}') +print(r'\end{center}') +\end{pycode} + +There is also a \texttt{pyconsole} environment that emulates a Python interactive console. For example, +\begin{pygments}{text} +\begin{pyconsole} +var = 1 + 1 +var +\end{pyconsole} +\end{pygments} +yields +\begin{pyconsole} +var = 1 + 1 +var +\end{pyconsole} + + +The \pygment{latex}{\begin} and \pygment{latex}{\end} of an environment should be on lines by themselves. Code in environments may be indented; see the \texttt{gobble} option in the documentation for more details. + +\section*{Macro programming} + +\pytex\ commands can be used inside other commands in macro programming. They will usually work fine, but curly braces should be used as delimiters and special \LaTeX\ characters such as \texttt{\%} and \texttt{\#} should be avoided in the Python code. \pytex\ environments cannot be used inside \LaTeX\ commands, due to the way \LaTeX\ deals with verbatim content and catcodes. + + +\section*{Additional features} + +\pytex\ provides many additional features. The working and output directories can be specified. The user can determine when code is executed with the package option \texttt{rerun}, based on factors such as modification and exit status. By default, all commands and environments run in a single session, providing continuity. Commands and environments accept an optional argument that specifies the session in which the code is executed; sessions run in parallel. \pytex\ provides a utilities class that is always imported into each session. The utilities class provides methods for tracking dependencies and automatically cleaning up created files. + +\pytex\ also provides the \texttt{depythontex} utility, which creates a copy of a document in which all \pytex\ commands and environments have been replaced by their output. The resulting document is more suitable for journal submission, sharing, and conversion to other document formats. + + + +\end{document} diff --git a/Master/texmf-dist/scripts/pythontex/depythontex.py b/Master/texmf-dist/scripts/pythontex/depythontex.py new file mode 100755 index 00000000000..013e268241f --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/depythontex.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +''' +This is the PythonTeX wrapper script. It automatically detects the version +of Python, and then imports the correct code from depythontex2.py or +depythontex3.py. + +Copyright (c) 2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + +import sys +if sys.version_info[0] == 2: + import depythontex2 as depythontex +elif sys.version_info[0] == 3: + import depythontex3 as depythontex diff --git a/Master/texmf-dist/scripts/pythontex/depythontex2.py b/Master/texmf-dist/scripts/pythontex/depythontex2.py new file mode 100755 index 00000000000..028badcb88e --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/depythontex2.py @@ -0,0 +1,1404 @@ +# -*- coding: utf-8 -*- +''' +PythonTeX depythontex script. + +This script takes a LaTeX document that uses the PythonTeX package and +creates a new document that does not depend on PythonTeX. It substitutes all +externally generated content into a copy of the original LaTeX document. +This is useful when you need a document that relies on few external packages +or custom macros (for example, for submission to a journal or conversion to +another document format). + +If you just want to share a document that uses PythonTeX, keep in mind that +the document can be modified and compiled just like a regular LaTeX document, +without needing Python or any other external tools, so long as the following +conditions are met: + + * A copy of pythontex.sty is included with the document. + * The pythontex-files-<name> directory is included with the document. + * The PythonTeX-specific parts of the document are not modified. + +To work, this script requires that the original LaTeX document be compiled +with the package option `depythontex`. That creates an auxiliary file with +the extension .depytx that contains information about all content that needs +to be substituted. + +This script is purposely written in a simple, largely linear form to +facilitate customization. Most of the key substitutions are performed by a +few functions defined near the beginning of the script, so if you need custom +substitutions, you should begin there. By default, all typeset code is +wrapped in `\verb` commands and verbatim environments, since these have the +greatest generality. However, the command-line option --listing allows code +to be typeset with the fancyvrb, listings, minted, or PythonTeX packages +instead. + +The script automatically extracts all arguments of all commands and +environments that it replaces, so that these are available if desired for +customized substitution. Two additional pieces of information are also +available for any typeset code: the Pygments lexer (often the same as the +language) and the starting line number (if line numbering was used). + +Keep in mind that some manual adjustments may be required after a document is +depythontex'ed. While depythontex attempts to create an exact copy of the +original document, in many cases an identical copy is impossible. For +example, typeset code may have a different appearance or layout when it is +typeset with a different package. + + +Copyright (c) 2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +#// Python 2 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +#\\ End Python 2 +import sys +import os +#// Python 2 +from io import open +input = raw_input +#\\ End Python 2 +import argparse +from collections import defaultdict +from re import match, sub, search +import textwrap + + +# Script parameters +# Version +version = 'v0.12' + + + + +# Functions and parameters for customizing the script output + +# Style or package for replacing code listings +# This is actually set via command-line option --listing +# It is created here simply for reference +listing = None #'verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex' + +# List of things to add to the preamble +# It can be appended to via the command-line option --preamble +# It is also appended to based on the code listing style that is used +# And it could be manually edited here as well, as long as it remains a list +preamble_additions = list() + +# Lexer dict +# If you are using Pygments lexers that don't directly correspond to the +# languages used by the listings package, you can submit replacements via the +# command line option --lexer-dict, or edit this dict manually here. When +# listings is used, all lexers are checked against this dict to see if a +# substitution should be made. This approach could easily be modified to +# work with another, non-Pygments highlighting package. +lexer_dict = dict() + + +def replace_code_cmd(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from a command with a command. + + It is only ever called if there is indeed code to typeset. + + Usually, code from a command is also typeset with a command. This + function primarily deals with that case. In cases where code from a + command is typeset with an environment (for example, `\inputpygments`), + this function performs some preprocessing and then uses + replace_code_env() to do the real work. This approach prevents the two + functions from unnecessarily duplicating each other, while still giving + the desired output. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command; the last argument is what is typeset, unless a + code_replacement is specified or other instructions are given + linenum (int): line number in the original TeX document + code_replacement (str/None): replacement for the code; usually None + for commands, because typically the code to be typeset is the + last argument passed to the command, rather than something + captured elsewhere (like the body of an environment) or something + preprocessed (like a console environment's content) + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the command; usually + shouldn't be needed + lexer (str/None): Pygments lexer + Returns: + (replacement, after) (tuple, of str) + + ''' + # Get the correct replacement + if code_replacement is None: + code_replacement = arglist[-1] + + # We only consider two possible modes of typesetting, verbatim and inline + # verbatim + if code_replacement_mode == 'verbatim': + # Sometimes we must replace a command with an environment, for + # example, for `\inputpygments` + + # Make sure the introduction of an environment where a command was + # previously won't produce errors with following content; make sure + # that any following content is on a separate line + if bool(match('[ \t]*\S', after)): + after = '\n' + after + # Rather than duplicating much of replace_code_env(), just use it + return replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber) + else: + # Usually, we're replacing a command with a command + + # Wrap the replacement in appropriate delimiters + if (listing in ('verbatim', 'fancyvrb', 'minted') or + (listing in ('listings', 'pythontex') and + ('{' in code_replacement or '}' in code_replacement))): + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in code_replacement: + break + code_replacement = delim + code_replacement + delim + else: + code_replacement = '{' + code_replacement + '}' + # Assemble the actual replacement + if listing in ('verbatim', 'minted'): # `\mint` isn't for inline use + code_replacement = r'\verb' + code_replacement + elif listing == 'fancyvrb': + code_replacement = r'\Verb' + code_replacement + elif listing == 'listings': + if lexer is None: + code_replacement = r'\lstinline[language={}]' + code_replacement + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + code_replacement = r'\lstinline[language=' + lexer + ']' + code_replacement + elif listing == 'pythontex': + if lexer is None: + code_replacement = r'\pygment{text}' + code_replacement + else: + code_replacement = r'\pygment{' + lexer + '}' + code_replacement + return (code_replacement, after) + + +def replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from an environment with an environment. + + It is only ever called if there is indeed code to typeset. + + Usually it is only used to typeset code from an environment. However, + some commands bring in code that must be typeset as an environment. In + those cases, replace_code_cmd() is called initially, and after it + performs some preprocessing, this function is called. This approach + avoids unnecessary duplication between the two functions. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where + the environment began + code_replacement (str): replacement for the code; unlike the case of + commands, this is always not None if the function is called + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the environment; usually + shouldn't be needed + lexer (str/None): Pygments lexer + firstnumber (str/None): the first number of the listing, if the listing + had numbered lines + Returns: + (replacement, after) (tuple, of str) + + ''' + # Currently, there is no need to test for code_replacement_mode, because + # this function is only ever called if the mode is 'verbatim'. That may + # change in the future, but it seems unlikely that code entered in an + # environment would end up typeset with a command. + if listing == 'verbatim': + pre = '\\begin{verbatim}' + post = '\\end{verbatim}' + elif listing == 'fancyvrb': + if firstnumber is None: + pre = '\\begin{Verbatim}' + else: + pre = '\\begin{{Verbatim}}[numbers=left,firstnumber={0}]'.format(firstnumber) + post = '\\end{Verbatim}' + elif listing == 'listings': + if lexer is None: + if firstnumber is None: + pre = '\\begin{lstlisting}[language={}]' + else: + pre = '\\begin{{lstlisting}}[language={{}},numbers=left,firstnumber={0}]'.format(firstnumber) + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + if firstnumber is None: + pre = '\\begin{{lstlisting}}[language={0}]'.format(lexer) + else: + pre = '\\begin{{lstlisting}}[language={0},numbers=left,firstnumber={1}]'.format(lexer, firstnumber) + post = '\\end{lstlisting}' + elif listing == 'minted': + if lexer is None: + if firstnumber is None: + pre = '\\begin{minted}{text}' + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{minted}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{minted}' + elif listing == 'pythontex': + if lexer is None: + if firstnumber is None: + pre = '\\begin{pygments}{text}' + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{pygments}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{pygments}' + code_replacement = pre + code_replacement + post + return (code_replacement, after) + + +# We will need to issue a warning every time that a substitution of printed +# content results in a forced double space. We could just do this as we go, +# but it's easier for the user to read if we just collect all the warnings +# of this type, and print them once. +forced_double_space_list = list() + + +def replace_print_cmd(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from a command. + + It is only ever called if there is indeed printed content to typeset. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command + linenum (int): line number in the original TeX document + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + elif print_replacement_mode == 'verbatim': + if bool(match('\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content from a file is included as LaTeX code, we have + # to be particularly careful to ensure that the content produces the + # same output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + # + # It turns out that the same approach is needed when a command like + # `\py` brings in content ending in a newline + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + elif print_replacement.endswith('\n'): + # We can't just use `else` because that would catch content + # from `\py` and similar + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip`. Since this is inline, the `\unskip` must + # immediately follow the command to do any good; otherwise, + # it eliminates spaces that precede it, but doesn't get into + # the `\input` content. + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\\unskip\s+', '', after) + elif bool(match('\S', after)): + # If the next character is not whitespace, we can just leave + # the `\n`, and it will yield a space. + pass + elif bool(match('\s*$', after)): + # If the rest of the current line, and the next line, are + # whitespace, we will get the correct spacing without needing + # `\space{}`. We could leave `\n`, but it would be + # extraneous whitespace. + print_replacement = print_replacement[:-1] + else: + # Otherwise, we do need to insert `\space{}` + # We keep the newline at the end of printed content, in case + # it's at the end of an environment, and thus is needed to + # protect the following content + print_replacement += '\\space{}' + after = sub('^\s+', '', after) + forced_double_space_list.append((name, linenum)) + else: + if bool(match('\s+\S', after)): + # If the following line starts with whitespace, replace it + # with a newline, to protect in the event that the printed + # content ended with an end-of-environment command + after = sub('^\s+', '\n', after) + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + +def replace_print_env(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from an environment. + + It is only ever called if there is indeed printed content to typeset. + + This should be similar to replace_print_cmd(). The main difference is + that the environment context typically ends with a newline, so + substitution has to be a little different to ensure that spacing after + the environment isn't modified. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where the + environment began + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + #### The inlineverb and verb modes should work, but haven't been tested + since there are currently no environments that use them; they are only + used by `\printpythontex`, which is a command. + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + elif print_replacement_mode == 'verbatim': + if bool(match('\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content is included as LaTeX code, we have to be + # particularly careful to ensure that the content produces the same + # output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + else: + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\s*\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip` + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\s*\\unskip\s+', '', after) + elif bool(match('[ \t]+\S', after)): + # If the next character after the end of the environment is + # not whitespace (usually not allowed), we can just leave + # the `\n` in printed content, and it will yield a space. + # So we need do nothing. But if there is text on that line + # we need `\space{}`. + after = sub('^\s+', '\\space', after) + forced_double_space_list.append((name, linenum)) + else: + # If the line at the end of the environment is blank, + # we can just discard it and keep the newline at the end of + # the printed content; the newline gives us the needed space + after = after.split('\n', 1)[1] + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + + + +# Deal with argv +# Parse argv +parser = argparse.ArgumentParser() +parser.add_argument('--version', action='version', + version='DePythonTeX {0}'.format(version)) +parser.add_argument('--encoding', default='utf-8', + help='encoding for all text files (see codecs module for encodings)') +parser.add_argument('--overwrite', default=False, action='store_true', + help='overwrite existing output, if it exists (off by default)') +parser.add_argument('--listing', default='verbatim', + choices=('verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex'), + help='style or package used for typesetting code') +parser.add_argument('--lexer-dict', default=None, + help='add mappings from Pygments lexer names to the language names of other highlighting packages; should be a comma-separated list of the form "<Pygments lexer>:<language>, <Pygments lexer>:<language>, ..."') +parser.add_argument('--preamble', default=None, + help='line of commands to add to output preamble') +parser.add_argument('--graphicspath', default=False, action='store_true', + help=r'Add the outputdir to the graphics path, by modifying an existing \graphicspath command or adding one.') +parser.add_argument('TEXNAME', + help='LaTeX file') +parser.add_argument('OUTFILE', nargs='?', default=None, + help='output file; by default, <filename>.<ext> is converted into depythontex_<filename>.<ext>') +args = parser.parse_args() + +# Process argv +encoding = args.encoding +listing = args.listing +if args.preamble is not None: + preamble_additions.append(args.preamble) +if args.lexer_dict is not None: + args.lexer_dict = args.lexer_dict.replace(' ', '').replace("'", "").replace('"','').strip('{}') + for entry in args.lexer_dict.split(','): + k, v = entry.split(':') + lexer_dict[k] = v +if args.listing == 'verbatim': + # In some contexts, the verbatim package might be desirable. + # But we assume that the user wants minimal packages. + # Also, the default verbatim environment doesn't allow text to follow the + # end-of-environment command. + # If the verbatim package is ever desired, simply uncomment the following: + # preamble_additions.append('\\usepackage{verbatim}') + pass +elif args.listing == 'fancyvrb': + preamble_additions.append('\\usepackage{fancyvrb}') +elif args.listing == 'listings': + preamble_additions.append('\\usepackage{listings}') +elif args.listing == 'minted': + preamble_additions.append('\\usepackage{minted}') +elif args.listing == 'pythontex': + preamble_additions.append('\\usepackage{pythontex}') + + + + +# Let the user know things have started +print('This is DePythonTeX {0}'.format(version)) +sys.stdout.flush() + + + + +# Make sure we have a valid texfile +texfile_name = os.path.expanduser(os.path.normcase(args.TEXNAME)) +if not os.path.isfile(texfile_name): + resolved = False + if not texfile_name.endswith('.tex'): + for ext in ('.tex', '.ltx', '.dtx'): + if os.path.isfile(texfile_name + ext): + texfile_name = texfile_name + ext + resolved = True + break + if not resolved: + print('* DePythonTeX error:') + print(' Could not locate file "' + texfile_name + '"') + sys.exit(1) +# Make sure we have a valid outfile +if args.OUTFILE is None: + p, f_name = os.path.split(texfile_name) + outfile_name = os.path.join(p, 'depythontex_' + f_name) +else: + outfile_name = os.path.expanduser(os.path.normcase(args.OUTFILE)) +if not args.overwrite and os.path.isfile(outfile_name): + print('* DePythonTeX warning:') + print(' Output file "' + outfile_name + '" already exists') + ans = input(' Do you want to overwrite this file? [y,n]\n ') + if ans != 'y': + sys.exit(1) +# Make sure the .depytx file exists +depytxfile_name = texfile_name.rsplit('.')[0] + '.depytx' +if not os.path.isfile(depytxfile_name): + print('* DePythonTeX error:') + print(' Could not find DePythonTeX auxiliary file "' + depytxfile_name + '"') + print(' Use package option depythontex to creat it') + sys.exit(1) + + + + +# Start opening files and loading data +# Read in the LaTeX file +# We read into a list with an empty first entry, so that we don't have to +# worry about zero indexing when comparing list index to file line number +f = open(texfile_name, 'r', encoding=encoding) +tex = [''] +tex.extend(f.readlines()) +f.close() +# Load the .depytx +f = open(depytxfile_name, 'r', encoding=encoding) +depytx = f.readlines() +f.close() +# Process the .depytx by getting the settings contained in the last few lines +settings = dict() +n = len(depytx) - 1 +while depytx[n].startswith('=>DEPYTHONTEX:SETTINGS#'): + content = depytx[n].split('#', 1)[1].rsplit('#', 1)[0] + k, v = content.split('=', 1) + if v in ('true', 'True'): + v = True + elif v in ('false', 'False'): + v = False + settings[k] = v + depytx[n] = '' + n -= 1 +# Check .depytx version to make sure it is compatible +if settings['version'] != version: + print('* DePythonTeX warning:') + print(' Version mismatch with DePythonTeX auxiliary file') + print(' Do a complete compile cycle to update the auxiliary file') + print(' Attempting to proceed') +# Go ahead and open the outfile, even though we don't need it until the end +# This lets us change working directories for convenience without worrying +# about having to modify the outfile path +outfile = open(outfile_name, 'w', encoding=encoding) + + + + +# Change working directory to the document directory +# Technically, we could get by without this, but that would require a lot of +# path modification. This way, we can just use all paths straight out of the +# .depytx without any modification, which is much simpler and less error-prone. +if os.path.split(texfile_name)[0] != '': + os.chdir(os.path.split(texfile_name)[0]) + + + + +# Open and process the file of macros +# Read in the macros +if os.path.isfile(settings['macrofile']): + f = open(settings['macrofile'], 'r', encoding=encoding) + macros = f.readlines() + f.close() +else: + print('* DePythonTeX error:') + print(' The macro file could not be found:') + print(' "' + settings['macrofile'] + '"') + print(' Run PythonTeX to create it') + sys.exit(1) +# Create a dict for storing macros +macrodict = defaultdict(list) +# Create variables for keeping track of whether we're inside a macro or +# environment +# These must exist before we begin processing +inside_macro = False +inside_environment = False +# Loop through the macros, and extract everything +# We just extract content; we get content wrappers later, when we process all +# substituted content +for line in macros: + if inside_macro: + # If we're in a macro, look for the end-of-macro command + if r'\endpytx@SVMCR' in line: + # If the current line contains the end-of-macro command, split + # off any content that comes before it. Also reset + # `inside_macro`. + macrodict[current_macro].append(line.rsplit(r'\endpytx@SVMCR', 1)[0]) + inside_macro = False + else: + # If the current line doesn't end the macro, we add the whole + # line to the macro dict + macrodict[current_macro].append(line) + elif inside_environment: + if line.startswith(end_environment): + # If the environment is ending, we reset inside_environment + inside_environment = False + else: + # If we're still in the environment, add the current line to the + # macro dict + macrodict[current_macro].append(line) + else: + # If we're not in a macro or environment, we need to figure out which + # we are dealing with (if either; there are blank lines in the macro + # file to increase readability). Once we've determined which one, + # we need to get its name and extract any content. + if line.startswith(r'\begin{'): + # Any \begin will indicate a use of fancyvrb to save verbatim + # content, since that is the only time an environment is used in + # the macro file. All other content is saved in a standard macro. + # We extract the name of the macro in which the verbatim content + # is saved. + current_macro = line.rsplit('{', 1)[1].rstrip('}\n') + inside_environment = True + # We assemble the end-of-environment string we will need to look + # for. We don't assume any particular name, for generality. + end_environment = r'\end{' + line.split('}', 1)[0].split('{', 1)[1] + '}' + # Code typset in an environment needs to have a leading newline, + # because the content of a normal verbatim environment keeps its + # leading newline. + macrodict[current_macro].append('\n') + elif line.startswith(r'\pytx@SVMCR{'): + # Any regular macro will use `\pytx@SVMCR` + current_macro = line.split('{', 1)[1].split('}', 1)[0] + inside_macro = True + # Any content will always be on the next line, so we don't need + # to check for it + + + + +# Do the actual processing +# Create a variable for keeping track of the current line in the LaTeX file +# Start at 1, since the first entry in the tex list is `''` +texlinenum = 1 +# Create a variable for storing the current line(s) we are processing. +# This contains all lines from immediately after the last successfully +# processed line up to and including texlinenum. We may have to process +# multiple lines at once if a macro is split over multiple lines, etc. +texcontent = tex[texlinenum] +# Create a list for storing processed content. +texout = list() +# Loop through the depytx and process +for n, depytxline in enumerate(depytx): + if depytxline.startswith('=>DEPYTHONTEX#'): + # Process info + depytxcontent = depytxline.split('#', 1)[1].rstrip('#\n') + depy_type, depy_name, depy_args, depy_typeset, depy_linenum, depy_lexer = depytxcontent.split(':') + if depy_lexer == '': + depy_lexer = None + + # Do a quick check on validity of info + # #### Eventually add 'cp' and 'pc' + if not (depy_type in ('cmd', 'env') and + all([letter in ('o', 'm', 'v', 'n', '|') for letter in depy_args]) and + ('|' not in depy_args or (depy_args.count('|') == 1 and depy_args.endswith('|'))) and + depy_typeset in ('c', 'p', 'n')): + print('* PythonTeX error:') + print(' Invalid \\Depythontex string for operation on line ' + str(depy_linenum)) + print(' The offending string was ' + depytxcontent) + sys.exit(1) + # If depy_args contains a `|` to indicate `\obeylines`, strip it and + # store in a variable. Create a bool to keep track of obeylines + # status, which governs whether we can look on the next line for + # arguments. (If obeylines is active, a newline terminates the + # argument search.) + if depy_args.endswith('|'): + obeylines = True + depy_args = depy_args.rstrip('|') + else: + obeylines = False + # Get the line number as an integer + # We don't have to adjust for zero indexing in tex + depy_linenum = int(depy_linenum) + + + # Check for information passed from LaTeX + # This will be extra listings information, or replacements to plug in + code_replacement = None + code_replacement_mode = None + print_replacement = None + print_replacement_mode = None + firstnumber = None + source = None + scan_ahead_line = n + 1 + nextdepytxline = depytx[scan_ahead_line] + while not nextdepytxline.startswith('=>DEPYTHONTEX#'): + if nextdepytxline.startswith('LISTING:'): + listingcontent = nextdepytxline.split(':', 1)[1].rstrip('\n') + if bool(match(r'firstnumber=\d+$', listingcontent)): + firstnumber = listingcontent.split('=', 1)[1] + else: + print('* DePythonTeX error:') + print(' Unknown information in listings data on line ' + str(depy_linenum)) + print(' The listings content was "' + listingcontent + '"') + sys.exit(1) + elif nextdepytxline.startswith('MACRO:'): + source = 'macro' + try: + typeset, macro = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + if macro not in macrodict: + print('* DePythonTeX error:') + print(' Could not find replacement content for macro "' + macro + '"') + print(' This is probably because the document needs to be recompiled') + sys.exit(1) + if typeset == 'c': + if depy_type == 'cmd': + code_replacement = ''.join(macrodict[macro]).strip('\n') + else: + code_replacement = ''.join(macrodict[macro]) + elif typeset == 'p': + print_replacement = ''.join(macrodict[macro]) + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + elif nextdepytxline.startswith('FILE:'): + source = 'file' + try: + typeset, f_name = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Files that are brought in have an optional mode that + # determines if they need special handling (for example, verbatim) + if ':mode=' in f_name: + f_name, mode = f_name.split(':mode=') + else: + mode = None + f = open(f_name, 'r', encoding=encoding) + replacement = f.read() + f.close() + if typeset == 'c': + code_replacement_mode = mode + if depy_type == 'cmd' and code_replacement_mode != 'verbatim': + # Usually, code from commands is typeset with commands + # and code from environments is typeset in + # environments. The except is code from commands + # that bring in external files, like `\inputpygments` + code_replacement = replacement + else: + # If we're replacing an environment of code with a + # file, then we lose the newline at the beginning + # of the environment, and need to get it back. + code_replacement = '\n' + replacement + elif typeset == 'p': + print_replacement_mode = mode + print_replacement = replacement + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Increment the line in depytx to check for more information + # from LaTeX + scan_ahead_line += 1 + if scan_ahead_line == len(depytx): + break + else: + nextdepytxline = depytx[scan_ahead_line] + + + # If the line we're looking for is within the range currently held by + # texcontent, do nothing. Otherwise, transfer content from tex + # to texout until we get to the line of tex that we're looking for + if depy_linenum > texlinenum: + texout.append(texcontent) + texlinenum += 1 + while texlinenum < depy_linenum: + texout.append(tex[texlinenum]) + texlinenum += 1 + texcontent = tex[texlinenum] + + + # Deal with arguments + # All arguments are parsed and stored in a list variables, even if + # they are not used, for completeness; this makes it easy to add + # functionality + # Start by splitting the current line into what comes before the + # command or environment, and what is after it + if depy_type == 'cmd': + try: + before, after = texcontent.split('\\' + depy_name, 1) + except: + print('* DePythonTeX error:') + print(' Could not find command "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: # depy_type == 'env': + try: + before, after = texcontent.split(r'\begin{' + depy_name + '}', 1) + except: + print('* DePythonTeX error:') + print(' Could not find environment "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # We won't need the content from before the command or environment + # again, so we go ahead and store it + texout.append(before) + + # Parse the arguments + # Create a list for storing the recovered arguments + arglist = list() + for argindex, arg in enumerate(depy_args): + if arg == 'n': + pass + elif arg == 'o': + if after[0] == '[': + # Account for possible line breaks before end of arg + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after[1:].split(']', 1) + else: + if obeylines: + # Take into account possible whitespace before arg + if bool(match('[ \t]*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # If this is the last arg, and it wasn't found, + # the macro should eat all whitespace following it + if argindex == len(depy_args) - 1: + after = sub('^[ \t]*', '', after) + else: + # Allow peeking ahead a line for the argument + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + # Take into account possible whitespace before arg + if bool(match('\s*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # Account for eating whitespace afterward, if arg not found + if argindex == len(depy_args) - 1: + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if not bool(match('\s*$', after)): + after = sub('^\s*', '', after) + arglist.append(optarg) + elif arg == 'm': + # Account for possible line breaks or spaces before arg + if after[0] == '{': + after = after[1:] + else: + if obeylines: + # Account for possible leading whitespace + if bool(match('[ \t\f\v]*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: + # Peek ahead a line if needed + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if bool(match('\s*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # Go through the argument character by character to find the + # closing brace. + # If possible, use a very simple approach + if (r'\{' not in after and r'\}' not in after and + r'\string' not in after and + after.count('{') + 1 == after.count('}')): + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + # If a simple parsing approach won't work, parse in much + # greater depth + else: + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + # If the current character is a brace, we count it + lbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos] == '}': + # If the current character is a brace, we count it + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos:].startswith(r'\string'): + # If the current position marks the beginning of `\string`, we + # resolve the `\string` command + # First, jump ahead to after `\string` + pos += 7 #+= len(r'\string') + # See if `\string` is followed by a regular macro + # If so, jump past it; otherwise, figure out if a + # single-character macro, or just a single character, is next, + # and jump past it + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + elif line[pos] == '\\': + pos += 2 + else: + pos += 1 + elif line[pos] == '\\': + # If the current position is a backslash, figure out what + # macro is used, and jump past it + # The macro must either be a standard alphabetic macro, + # or a single-character macro + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + else: + pos += 2 + else: + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[:pos] + after = after[pos+1:] + arglist.append(mainarg) + elif arg == 'v': + if after[0] == '{': + # Account for the possibility of matched brace delimiters + # Not all verbatim commands allow for these + pos = 1 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[1:pos] + after = after[pos+1:] + else: + # Deal with matched character delims + delim = after[0] + while after.count(delim) < 2: + texlinenum += 1 + after += tex[texlinenum] + mainarg, after = after[1:].split(delim, 1) + arglist.append(mainarg) + + + # Do substitution, depending on what is required + # Need a variable for processed content to be added to texout + processed = None + if depy_typeset == 'c': + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_cmd(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if code_replacement is None: + if end_environment not in after: + while True: + texlinenum += 1 + after += tex[texlinenum] + if end_environment in tex[texlinenum]: + break + code_replacement, after = after.split(end_environment, 1) + # If there's content on the line with the end-environment + # command, it should be discarded, to imitate TeX + if not code_replacement.endswith('\n'): + code_replacement = code_replacement.rsplit('\n', 1)[0] + '\n' + # Take care of `gobble` + if settings['gobble'] == 'auto': + code_replacement = textwrap.dedent(code_replacement) + else: + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_env(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + elif depy_typeset == 'p' and print_replacement is not None: + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_cmd(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_env(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_typeset == 'n' or (depy_typeset == 'p' and print_replacement is None): + if depy_type == 'cmd': + texcontent = after + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + if bool(match('\s*\n', after)): + # If the line following `after` is whitespace, it should + # be stripped, since most environments throw away + # anything after the end of the environment + after = after.split('\n')[1] + texcontent = after + # #### Once it's supported on the TeX side, need to add support for + # pc and cp + + + # Store any processed content + if processed is not None: + texout.append(processed) + + +# Transfer anything that's left in tex to texout +texout.append(texcontent) +texout.extend(tex[texlinenum+1:]) + + + + +# Replace the `\usepackage{pythontex}` +for n, line in enumerate(texout): + if '{pythontex}' in line: + startline = n + while '\\usepackage' not in texout[startline] and startline >= 0: + startline -= 1 + if startline == n: + if bool(search(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', line)): + texout[n] = sub(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', '', line) + break + else: + content = ''.join(texout[startline:n+1]) + if bool(search(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', content)): + replacement = sub(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', '', content) + texout[startline] = replacement + for l in range(startline+1, n+1): + texout[l] = '' + break + elif line.startswith(r'\begin{document}'): + break +if preamble_additions: + texout[n] += '\n'.join(preamble_additions) + '\n' +# Take care of graphicspath +if args.graphicspath and settings['graphicx']: + for n, line in enumerate(texout): + if '\\graphicspath' in line and not bool(match('\s*%', line)): + texout[n] = line.replace('\\graphicspath{', '\\graphicspath{{' + settings['outputdir'] +'/}') + break + elif line.startswith(r'\begin{document}'): + texout[n] = '\\graphicspath{{' + settings['outputdir'] + '/}}\n' + line + break + + + + +# Print any final messages +if forced_double_space_list: + print('* DePythonTeX warning:') + print(' A trailing double space was forced with "\\space{}" for the following') + print(' This can happen when printed content is included inline') + print(' The forced double space is only an issue if it is not intentional') + for name, linenum in forced_double_space_list: + print(' "' + name + '" near line ' + str(linenum)) + + + + +# Write output +for line in texout: + outfile.write(line) +outfile.close() diff --git a/Master/texmf-dist/scripts/pythontex/depythontex3.py b/Master/texmf-dist/scripts/pythontex/depythontex3.py new file mode 100755 index 00000000000..996b8447316 --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/depythontex3.py @@ -0,0 +1,1404 @@ +# -*- coding: utf-8 -*- +''' +PythonTeX depythontex script. + +This script takes a LaTeX document that uses the PythonTeX package and +creates a new document that does not depend on PythonTeX. It substitutes all +externally generated content into a copy of the original LaTeX document. +This is useful when you need a document that relies on few external packages +or custom macros (for example, for submission to a journal or conversion to +another document format). + +If you just want to share a document that uses PythonTeX, keep in mind that +the document can be modified and compiled just like a regular LaTeX document, +without needing Python or any other external tools, so long as the following +conditions are met: + + * A copy of pythontex.sty is included with the document. + * The pythontex-files-<name> directory is included with the document. + * The PythonTeX-specific parts of the document are not modified. + +To work, this script requires that the original LaTeX document be compiled +with the package option `depythontex`. That creates an auxiliary file with +the extension .depytx that contains information about all content that needs +to be substituted. + +This script is purposely written in a simple, largely linear form to +facilitate customization. Most of the key substitutions are performed by a +few functions defined near the beginning of the script, so if you need custom +substitutions, you should begin there. By default, all typeset code is +wrapped in `\verb` commands and verbatim environments, since these have the +greatest generality. However, the command-line option --listing allows code +to be typeset with the fancyvrb, listings, minted, or PythonTeX packages +instead. + +The script automatically extracts all arguments of all commands and +environments that it replaces, so that these are available if desired for +customized substitution. Two additional pieces of information are also +available for any typeset code: the Pygments lexer (often the same as the +language) and the starting line number (if line numbering was used). + +Keep in mind that some manual adjustments may be required after a document is +depythontex'ed. While depythontex attempts to create an exact copy of the +original document, in many cases an identical copy is impossible. For +example, typeset code may have a different appearance or layout when it is +typeset with a different package. + + +Copyright (c) 2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +#// Python 2 +#from __future__ import absolute_import +#from __future__ import division +#from __future__ import print_function +#from __future__ import unicode_literals +#\\ End Python 2 +import sys +import os +#// Python 2 +#from io import open +#input = raw_input +#\\ End Python 2 +import argparse +from collections import defaultdict +from re import match, sub, search +import textwrap + + +# Script parameters +# Version +version = 'v0.12' + + + + +# Functions and parameters for customizing the script output + +# Style or package for replacing code listings +# This is actually set via command-line option --listing +# It is created here simply for reference +listing = None #'verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex' + +# List of things to add to the preamble +# It can be appended to via the command-line option --preamble +# It is also appended to based on the code listing style that is used +# And it could be manually edited here as well, as long as it remains a list +preamble_additions = list() + +# Lexer dict +# If you are using Pygments lexers that don't directly correspond to the +# languages used by the listings package, you can submit replacements via the +# command line option --lexer-dict, or edit this dict manually here. When +# listings is used, all lexers are checked against this dict to see if a +# substitution should be made. This approach could easily be modified to +# work with another, non-Pygments highlighting package. +lexer_dict = dict() + + +def replace_code_cmd(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from a command with a command. + + It is only ever called if there is indeed code to typeset. + + Usually, code from a command is also typeset with a command. This + function primarily deals with that case. In cases where code from a + command is typeset with an environment (for example, `\inputpygments`), + this function performs some preprocessing and then uses + replace_code_env() to do the real work. This approach prevents the two + functions from unnecessarily duplicating each other, while still giving + the desired output. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command; the last argument is what is typeset, unless a + code_replacement is specified or other instructions are given + linenum (int): line number in the original TeX document + code_replacement (str/None): replacement for the code; usually None + for commands, because typically the code to be typeset is the + last argument passed to the command, rather than something + captured elsewhere (like the body of an environment) or something + preprocessed (like a console environment's content) + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the command; usually + shouldn't be needed + lexer (str/None): Pygments lexer + Returns: + (replacement, after) (tuple, of str) + + ''' + # Get the correct replacement + if code_replacement is None: + code_replacement = arglist[-1] + + # We only consider two possible modes of typesetting, verbatim and inline + # verbatim + if code_replacement_mode == 'verbatim': + # Sometimes we must replace a command with an environment, for + # example, for `\inputpygments` + + # Make sure the introduction of an environment where a command was + # previously won't produce errors with following content; make sure + # that any following content is on a separate line + if bool(match('[ \t]*\S', after)): + after = '\n' + after + # Rather than duplicating much of replace_code_env(), just use it + return replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber) + else: + # Usually, we're replacing a command with a command + + # Wrap the replacement in appropriate delimiters + if (listing in ('verbatim', 'fancyvrb', 'minted') or + (listing in ('listings', 'pythontex') and + ('{' in code_replacement or '}' in code_replacement))): + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in code_replacement: + break + code_replacement = delim + code_replacement + delim + else: + code_replacement = '{' + code_replacement + '}' + # Assemble the actual replacement + if listing in ('verbatim', 'minted'): # `\mint` isn't for inline use + code_replacement = r'\verb' + code_replacement + elif listing == 'fancyvrb': + code_replacement = r'\Verb' + code_replacement + elif listing == 'listings': + if lexer is None: + code_replacement = r'\lstinline[language={}]' + code_replacement + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + code_replacement = r'\lstinline[language=' + lexer + ']' + code_replacement + elif listing == 'pythontex': + if lexer is None: + code_replacement = r'\pygment{text}' + code_replacement + else: + code_replacement = r'\pygment{' + lexer + '}' + code_replacement + return (code_replacement, after) + + +def replace_code_env(name, arglist, linenum, code_replacement, + code_replacement_mode, after, lexer, firstnumber): + ''' + Typeset code from an environment with an environment. + + It is only ever called if there is indeed code to typeset. + + Usually it is only used to typeset code from an environment. However, + some commands bring in code that must be typeset as an environment. In + those cases, replace_code_cmd() is called initially, and after it + performs some preprocessing, this function is called. This approach + avoids unnecessary duplication between the two functions. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where + the environment began + code_replacement (str): replacement for the code; unlike the case of + commands, this is always not None if the function is called + code_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX; generally unused for code), + verb (inline), or verbatim (environment) + after (str): text immediately following the environment; usually + shouldn't be needed + lexer (str/None): Pygments lexer + firstnumber (str/None): the first number of the listing, if the listing + had numbered lines + Returns: + (replacement, after) (tuple, of str) + + ''' + # Currently, there is no need to test for code_replacement_mode, because + # this function is only ever called if the mode is 'verbatim'. That may + # change in the future, but it seems unlikely that code entered in an + # environment would end up typeset with a command. + if listing == 'verbatim': + pre = '\\begin{verbatim}' + post = '\\end{verbatim}' + elif listing == 'fancyvrb': + if firstnumber is None: + pre = '\\begin{Verbatim}' + else: + pre = '\\begin{{Verbatim}}[numbers=left,firstnumber={0}]'.format(firstnumber) + post = '\\end{Verbatim}' + elif listing == 'listings': + if lexer is None: + if firstnumber is None: + pre = '\\begin{lstlisting}[language={}]' + else: + pre = '\\begin{{lstlisting}}[language={{}},numbers=left,firstnumber={0}]'.format(firstnumber) + else: + if lexer in lexer_dict: + lexer = lexer_dict[lexer] + if firstnumber is None: + pre = '\\begin{{lstlisting}}[language={0}]'.format(lexer) + else: + pre = '\\begin{{lstlisting}}[language={0},numbers=left,firstnumber={1}]'.format(lexer, firstnumber) + post = '\\end{lstlisting}' + elif listing == 'minted': + if lexer is None: + if firstnumber is None: + pre = '\\begin{minted}{text}' + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{minted}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{minted}}[linenos,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{minted}' + elif listing == 'pythontex': + if lexer is None: + if firstnumber is None: + pre = '\\begin{pygments}{text}' + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{text}}'.format(firstnumber) + else: + if firstnumber is None: + pre = '\\begin{{pygments}}{{{0}}}'.format(lexer) + else: + pre = '\\begin{{pygments}}[numbers=left,firstnumber={0}]{{{1}}}'.format(firstnumber, lexer) + post = '\\end{pygments}' + code_replacement = pre + code_replacement + post + return (code_replacement, after) + + +# We will need to issue a warning every time that a substitution of printed +# content results in a forced double space. We could just do this as we go, +# but it's easier for the user to read if we just collect all the warnings +# of this type, and print them once. +forced_double_space_list = list() + + +def replace_print_cmd(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from a command. + + It is only ever called if there is indeed printed content to typeset. + + Args: + name (str): name of the command + arglist (list, of str/None): all arguments given to the original + command + linenum (int): line number in the original TeX document + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + elif print_replacement_mode == 'verbatim': + if bool(match('\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content from a file is included as LaTeX code, we have + # to be particularly careful to ensure that the content produces the + # same output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + # + # It turns out that the same approach is needed when a command like + # `\py` brings in content ending in a newline + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + elif print_replacement.endswith('\n'): + # We can't just use `else` because that would catch content + # from `\py` and similar + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip`. Since this is inline, the `\unskip` must + # immediately follow the command to do any good; otherwise, + # it eliminates spaces that precede it, but doesn't get into + # the `\input` content. + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\\unskip\s+', '', after) + elif bool(match('\S', after)): + # If the next character is not whitespace, we can just leave + # the `\n`, and it will yield a space. + pass + elif bool(match('\s*$', after)): + # If the rest of the current line, and the next line, are + # whitespace, we will get the correct spacing without needing + # `\space{}`. We could leave `\n`, but it would be + # extraneous whitespace. + print_replacement = print_replacement[:-1] + else: + # Otherwise, we do need to insert `\space{}` + # We keep the newline at the end of printed content, in case + # it's at the end of an environment, and thus is needed to + # protect the following content + print_replacement += '\\space{}' + after = sub('^\s+', '', after) + forced_double_space_list.append((name, linenum)) + else: + if bool(match('\s+\S', after)): + # If the following line starts with whitespace, replace it + # with a newline, to protect in the event that the printed + # content ended with an end-of-environment command + after = sub('^\s+', '\n', after) + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + +def replace_print_env(name, arglist, linenum, + print_replacement, print_replacement_mode, source, + after): + ''' + Typeset printed content from an environment. + + It is only ever called if there is indeed printed content to typeset. + + This should be similar to replace_print_cmd(). The main difference is + that the environment context typically ends with a newline, so + substitution has to be a little different to ensure that spacing after + the environment isn't modified. + + Args: + name (str): name of the environment + arglist (list, of str/None): all arguments given to the original + environment + linenum (int): line number in the original TeX document where the + environment began + print_replacement (str): printed content, read directly from file + into a single string + print_replacement_mode (str/None): mode in which the replacement is + to be typeset; raw/None (as TeX), inlineverb (or v) (as inline), + or verb (as environment) + source (str/None): source of the replacement content + after (str): text immediately following the command; important in + some situations, because spacing can depend on what's next + Returns: + (replacement, after) (tuple, of str) + + #### The inlineverb and verb modes should work, but haven't been tested + since there are currently no environments that use them; they are only + used by `\printpythontex`, which is a command. + ''' + if print_replacement_mode == 'verb': + if print_replacement.count('\n') > 1: + print('* DePythonTeX error:') + print(' Attempt to print multiple lines of content near line ' + str(linenum)) + print(' This is not possible in inline verbatim mode') + sys.exit(1) + print_replacement = print_replacement.rstrip('\n') + for delim in ('|', '/', '`', '!', '&', '#', '@', ':', '%', '~', '$', + '=', '+', '-', '^', '_', '?', ';'): + if delim not in print_replacement: + break + print_replacement = r'\verb' + delim + print_replacement + delim + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + elif print_replacement_mode == 'verbatim': + if bool(match('\s*?\n', after)): + # Usually, we would end the verbatim environment with a newline. + # This is fine if there is content in `after` before the next + # newline---in fact, it's desirable, because the verbatim package + # doesn't allow for content on the same line as the end of the + # environment. But if `after` is an empty line, then adding a + # newline will throw off spacing and must be avoided + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}' + else: + print_replacement = '\\begin{verbatim}\n' + print_replacement + '\\end{verbatim}\n' + else: + # When printed content is included as LaTeX code, we have to be + # particularly careful to ensure that the content produces the same + # output when substituted as when brought in by `\input`. In + # particular, `\input` strips newlines from each line of content and + # adds a space at the end of each line. This space is inside the + # `\input`, so it will not merge with following spaces. So when we + # substitute the content, sometimes we need to replace the final + # newline with a space that cannot be gobbled. + # + # It gets more complicated. This final space is often not + # desirable. It can be prevented by either printing an `\endinput` + # command, to terminate the `\input`, or printing a percent + # character % in the last line of the content, which comments out the + # final newline. So we must check for `\endinput` anywhere in + # printed content, and % in the final line, and remove any content + # after them. It's also possible that the print is followed by + # an `\unskip` that eats the space, so we need to check for that too. + if (print_replacement.endswith('\\endinput\n') and + not print_replacement.endswith('\\string\\endinput\n')): + # If `\endinput` is present, everything from it on should be + # discarded, unless the `\endinput` is not actually a command + # but rather a typeset name (for example, `\string\endinput` or + # `\verb|\endinput|`). It's impossible to check for all cases in + # which `\endinput` is not a command (at least, without actually + # using LaTeX), and even checking for most of them would require + # a good bit of parsing. We assume that `\endinput`, as a + # command, will only ever occur at the immediate end of the + # printed content. Later, we issue a warning in case it appears + # anywhere else. + print_replacement = print_replacement.rsplit(r'\endinput', 1)[0] + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + elif (print_replacement.endswith('%\n') and + not print_replacement.endswith('\\%\n') and + not print_replacement.endswith('\\string%\n')): + # Perform an analogous check for a terminating percent characer %. + # This case would be a bit easier to parse fully, since a percent + # that comments out the last newline would have to be in the + # final line of the replacement. But it would still be + # very difficult to perform a complete check. Later, we issue a + # warning if there is reason to think that a percent character + # was active in the last line. + print_replacement = print_replacement.rsplit(r'%', 1)[0] + if not bool(match('[ \t]+\S', after)): + # If there is text on the same line as the end of the + # environment, we're fine (this is unusual). Otherwise, + # we need to toss the newline at the end of the environment + # and gobble leading spaces. Leading spaces need to be + # gobbled because previously they were at the beginning of a + # line, where they would have been discarded. + if not bool(match('\s*$', after)): + after = sub('^\s*?\n\s*', '', after) + else: + # By default, LaTeX strips newlines and adds a space at the end + # of each line of content that is brought in by `\input`. This + # may or may not be desirable, but we replicate the effect here + # for consistency with the original document. We use `\space{}` + # because plain `\space` would gobble a following space, which + # isn't consistent with the `\input` behavior being replicated. + if bool(match(r'\s*\\unskip\s+\S', after)): + # If there's an `\unskip`, fix the spacing and remove the + # `\unskip` + print_replacement = print_replacement.rstrip(' \t\n') + after = sub(r'^\s*\\unskip\s+', '', after) + elif bool(match('[ \t]+\S', after)): + # If the next character after the end of the environment is + # not whitespace (usually not allowed), we can just leave + # the `\n` in printed content, and it will yield a space. + # So we need do nothing. But if there is text on that line + # we need `\space{}`. + after = sub('^\s+', '\\space', after) + forced_double_space_list.append((name, linenum)) + else: + # If the line at the end of the environment is blank, + # we can just discard it and keep the newline at the end of + # the printed content; the newline gives us the needed space + after = after.split('\n', 1)[1] + # Issue warnings, if warranted + # Warn about `\endinput` + if (r'\endinput' in print_replacement and + print_replacement.count(r'\endinput') != print_replacement.count(r'\string\endinput')): + print('* DePythonTeX warning:') + print(' "\\endinput" was present in printed content near line ' + str(linenum)) + print(' If this "\\endinput" was verbatim, you have nothing to worry about') + print(' If this "\\endinput" is to be active, it should be printed last') + print(' If you need "\\endinput" elsewhere, customize depythontex.py') + # Warn if it looks like there are active `%` that could comment + # out part of the original document. We only need to check the + # last line of printed content, because only there could + # percent characters escape from their original confines within + # `\input`, and comment out part of the document. + if print_replacement.endswith('\n'): + if print_replacement.count('\n') > 1: + last_line = print_replacement.rsplit('\n', 2)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following newline') + else: + if '\n' in print_replacement: + last_line = print_replacement.rsplit('\n', 1)[1] + else: + last_line = print_replacement + if last_line.count('%') != last_line.count(r'\%') + last_line.count(r'\string%'): + print('* DePythonTeX warning:') + print(' One or more percent characters are on the last line of ') + print(' printed content near line ' + str(linenum)) + print(' If they are not verbatim, they could comment out the following text') + # Warn if there were `\unskip`'s in the output, in case they weren't + # positioned correctly + if bool(match(r'\s*\\unskip', after)): + print('* DePythonTeX warning:') + print(' "\\unskip" survived substitution near line ' + str(linenum)) + print(' If it should have adjusted the spacing of printed content') + print(' you should double-check the spacing') + return (print_replacement, after) + + + + +# Deal with argv +# Parse argv +parser = argparse.ArgumentParser() +parser.add_argument('--version', action='version', + version='DePythonTeX {0}'.format(version)) +parser.add_argument('--encoding', default='utf-8', + help='encoding for all text files (see codecs module for encodings)') +parser.add_argument('--overwrite', default=False, action='store_true', + help='overwrite existing output, if it exists (off by default)') +parser.add_argument('--listing', default='verbatim', + choices=('verbatim', 'fancyvrb', 'listings', 'minted', 'pythontex'), + help='style or package used for typesetting code') +parser.add_argument('--lexer-dict', default=None, + help='add mappings from Pygments lexer names to the language names of other highlighting packages; should be a comma-separated list of the form "<Pygments lexer>:<language>, <Pygments lexer>:<language>, ..."') +parser.add_argument('--preamble', default=None, + help='line of commands to add to output preamble') +parser.add_argument('--graphicspath', default=False, action='store_true', + help=r'Add the outputdir to the graphics path, by modifying an existing \graphicspath command or adding one.') +parser.add_argument('TEXNAME', + help='LaTeX file') +parser.add_argument('OUTFILE', nargs='?', default=None, + help='output file; by default, <filename>.<ext> is converted into depythontex_<filename>.<ext>') +args = parser.parse_args() + +# Process argv +encoding = args.encoding +listing = args.listing +if args.preamble is not None: + preamble_additions.append(args.preamble) +if args.lexer_dict is not None: + args.lexer_dict = args.lexer_dict.replace(' ', '').replace("'", "").replace('"','').strip('{}') + for entry in args.lexer_dict.split(','): + k, v = entry.split(':') + lexer_dict[k] = v +if args.listing == 'verbatim': + # In some contexts, the verbatim package might be desirable. + # But we assume that the user wants minimal packages. + # Also, the default verbatim environment doesn't allow text to follow the + # end-of-environment command. + # If the verbatim package is ever desired, simply uncomment the following: + # preamble_additions.append('\\usepackage{verbatim}') + pass +elif args.listing == 'fancyvrb': + preamble_additions.append('\\usepackage{fancyvrb}') +elif args.listing == 'listings': + preamble_additions.append('\\usepackage{listings}') +elif args.listing == 'minted': + preamble_additions.append('\\usepackage{minted}') +elif args.listing == 'pythontex': + preamble_additions.append('\\usepackage{pythontex}') + + + + +# Let the user know things have started +print('This is DePythonTeX {0}'.format(version)) +sys.stdout.flush() + + + + +# Make sure we have a valid texfile +texfile_name = os.path.expanduser(os.path.normcase(args.TEXNAME)) +if not os.path.isfile(texfile_name): + resolved = False + if not texfile_name.endswith('.tex'): + for ext in ('.tex', '.ltx', '.dtx'): + if os.path.isfile(texfile_name + ext): + texfile_name = texfile_name + ext + resolved = True + break + if not resolved: + print('* DePythonTeX error:') + print(' Could not locate file "' + texfile_name + '"') + sys.exit(1) +# Make sure we have a valid outfile +if args.OUTFILE is None: + p, f_name = os.path.split(texfile_name) + outfile_name = os.path.join(p, 'depythontex_' + f_name) +else: + outfile_name = os.path.expanduser(os.path.normcase(args.OUTFILE)) +if not args.overwrite and os.path.isfile(outfile_name): + print('* DePythonTeX warning:') + print(' Output file "' + outfile_name + '" already exists') + ans = input(' Do you want to overwrite this file? [y,n]\n ') + if ans != 'y': + sys.exit(1) +# Make sure the .depytx file exists +depytxfile_name = texfile_name.rsplit('.')[0] + '.depytx' +if not os.path.isfile(depytxfile_name): + print('* DePythonTeX error:') + print(' Could not find DePythonTeX auxiliary file "' + depytxfile_name + '"') + print(' Use package option depythontex to creat it') + sys.exit(1) + + + + +# Start opening files and loading data +# Read in the LaTeX file +# We read into a list with an empty first entry, so that we don't have to +# worry about zero indexing when comparing list index to file line number +f = open(texfile_name, 'r', encoding=encoding) +tex = [''] +tex.extend(f.readlines()) +f.close() +# Load the .depytx +f = open(depytxfile_name, 'r', encoding=encoding) +depytx = f.readlines() +f.close() +# Process the .depytx by getting the settings contained in the last few lines +settings = dict() +n = len(depytx) - 1 +while depytx[n].startswith('=>DEPYTHONTEX:SETTINGS#'): + content = depytx[n].split('#', 1)[1].rsplit('#', 1)[0] + k, v = content.split('=', 1) + if v in ('true', 'True'): + v = True + elif v in ('false', 'False'): + v = False + settings[k] = v + depytx[n] = '' + n -= 1 +# Check .depytx version to make sure it is compatible +if settings['version'] != version: + print('* DePythonTeX warning:') + print(' Version mismatch with DePythonTeX auxiliary file') + print(' Do a complete compile cycle to update the auxiliary file') + print(' Attempting to proceed') +# Go ahead and open the outfile, even though we don't need it until the end +# This lets us change working directories for convenience without worrying +# about having to modify the outfile path +outfile = open(outfile_name, 'w', encoding=encoding) + + + + +# Change working directory to the document directory +# Technically, we could get by without this, but that would require a lot of +# path modification. This way, we can just use all paths straight out of the +# .depytx without any modification, which is much simpler and less error-prone. +if os.path.split(texfile_name)[0] != '': + os.chdir(os.path.split(texfile_name)[0]) + + + + +# Open and process the file of macros +# Read in the macros +if os.path.isfile(settings['macrofile']): + f = open(settings['macrofile'], 'r', encoding=encoding) + macros = f.readlines() + f.close() +else: + print('* DePythonTeX error:') + print(' The macro file could not be found:') + print(' "' + settings['macrofile'] + '"') + print(' Run PythonTeX to create it') + sys.exit(1) +# Create a dict for storing macros +macrodict = defaultdict(list) +# Create variables for keeping track of whether we're inside a macro or +# environment +# These must exist before we begin processing +inside_macro = False +inside_environment = False +# Loop through the macros, and extract everything +# We just extract content; we get content wrappers later, when we process all +# substituted content +for line in macros: + if inside_macro: + # If we're in a macro, look for the end-of-macro command + if r'\endpytx@SVMCR' in line: + # If the current line contains the end-of-macro command, split + # off any content that comes before it. Also reset + # `inside_macro`. + macrodict[current_macro].append(line.rsplit(r'\endpytx@SVMCR', 1)[0]) + inside_macro = False + else: + # If the current line doesn't end the macro, we add the whole + # line to the macro dict + macrodict[current_macro].append(line) + elif inside_environment: + if line.startswith(end_environment): + # If the environment is ending, we reset inside_environment + inside_environment = False + else: + # If we're still in the environment, add the current line to the + # macro dict + macrodict[current_macro].append(line) + else: + # If we're not in a macro or environment, we need to figure out which + # we are dealing with (if either; there are blank lines in the macro + # file to increase readability). Once we've determined which one, + # we need to get its name and extract any content. + if line.startswith(r'\begin{'): + # Any \begin will indicate a use of fancyvrb to save verbatim + # content, since that is the only time an environment is used in + # the macro file. All other content is saved in a standard macro. + # We extract the name of the macro in which the verbatim content + # is saved. + current_macro = line.rsplit('{', 1)[1].rstrip('}\n') + inside_environment = True + # We assemble the end-of-environment string we will need to look + # for. We don't assume any particular name, for generality. + end_environment = r'\end{' + line.split('}', 1)[0].split('{', 1)[1] + '}' + # Code typset in an environment needs to have a leading newline, + # because the content of a normal verbatim environment keeps its + # leading newline. + macrodict[current_macro].append('\n') + elif line.startswith(r'\pytx@SVMCR{'): + # Any regular macro will use `\pytx@SVMCR` + current_macro = line.split('{', 1)[1].split('}', 1)[0] + inside_macro = True + # Any content will always be on the next line, so we don't need + # to check for it + + + + +# Do the actual processing +# Create a variable for keeping track of the current line in the LaTeX file +# Start at 1, since the first entry in the tex list is `''` +texlinenum = 1 +# Create a variable for storing the current line(s) we are processing. +# This contains all lines from immediately after the last successfully +# processed line up to and including texlinenum. We may have to process +# multiple lines at once if a macro is split over multiple lines, etc. +texcontent = tex[texlinenum] +# Create a list for storing processed content. +texout = list() +# Loop through the depytx and process +for n, depytxline in enumerate(depytx): + if depytxline.startswith('=>DEPYTHONTEX#'): + # Process info + depytxcontent = depytxline.split('#', 1)[1].rstrip('#\n') + depy_type, depy_name, depy_args, depy_typeset, depy_linenum, depy_lexer = depytxcontent.split(':') + if depy_lexer == '': + depy_lexer = None + + # Do a quick check on validity of info + # #### Eventually add 'cp' and 'pc' + if not (depy_type in ('cmd', 'env') and + all([letter in ('o', 'm', 'v', 'n', '|') for letter in depy_args]) and + ('|' not in depy_args or (depy_args.count('|') == 1 and depy_args.endswith('|'))) and + depy_typeset in ('c', 'p', 'n')): + print('* PythonTeX error:') + print(' Invalid \\Depythontex string for operation on line ' + str(depy_linenum)) + print(' The offending string was ' + depytxcontent) + sys.exit(1) + # If depy_args contains a `|` to indicate `\obeylines`, strip it and + # store in a variable. Create a bool to keep track of obeylines + # status, which governs whether we can look on the next line for + # arguments. (If obeylines is active, a newline terminates the + # argument search.) + if depy_args.endswith('|'): + obeylines = True + depy_args = depy_args.rstrip('|') + else: + obeylines = False + # Get the line number as an integer + # We don't have to adjust for zero indexing in tex + depy_linenum = int(depy_linenum) + + + # Check for information passed from LaTeX + # This will be extra listings information, or replacements to plug in + code_replacement = None + code_replacement_mode = None + print_replacement = None + print_replacement_mode = None + firstnumber = None + source = None + scan_ahead_line = n + 1 + nextdepytxline = depytx[scan_ahead_line] + while not nextdepytxline.startswith('=>DEPYTHONTEX#'): + if nextdepytxline.startswith('LISTING:'): + listingcontent = nextdepytxline.split(':', 1)[1].rstrip('\n') + if bool(match(r'firstnumber=\d+$', listingcontent)): + firstnumber = listingcontent.split('=', 1)[1] + else: + print('* DePythonTeX error:') + print(' Unknown information in listings data on line ' + str(depy_linenum)) + print(' The listings content was "' + listingcontent + '"') + sys.exit(1) + elif nextdepytxline.startswith('MACRO:'): + source = 'macro' + try: + typeset, macro = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + if macro not in macrodict: + print('* DePythonTeX error:') + print(' Could not find replacement content for macro "' + macro + '"') + print(' This is probably because the document needs to be recompiled') + sys.exit(1) + if typeset == 'c': + if depy_type == 'cmd': + code_replacement = ''.join(macrodict[macro]).strip('\n') + else: + code_replacement = ''.join(macrodict[macro]) + elif typeset == 'p': + print_replacement = ''.join(macrodict[macro]) + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for macro information on line ' + str(depy_linenum)) + print(' The macro information was "' + nextdepytxline + '"') + sys.exit(1) + elif nextdepytxline.startswith('FILE:'): + source = 'file' + try: + typeset, f_name = nextdepytxline.rstrip('\n').split(':', 2)[1:] + except: + print('* DePythonTeX error:') + print(' Improperly formatted file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Files that are brought in have an optional mode that + # determines if they need special handling (for example, verbatim) + if ':mode=' in f_name: + f_name, mode = f_name.split(':mode=') + else: + mode = None + f = open(f_name, 'r', encoding=encoding) + replacement = f.read() + f.close() + if typeset == 'c': + code_replacement_mode = mode + if depy_type == 'cmd' and code_replacement_mode != 'verbatim': + # Usually, code from commands is typeset with commands + # and code from environments is typeset in + # environments. The except is code from commands + # that bring in external files, like `\inputpygments` + code_replacement = replacement + else: + # If we're replacing an environment of code with a + # file, then we lose the newline at the beginning + # of the environment, and need to get it back. + code_replacement = '\n' + replacement + elif typeset == 'p': + print_replacement_mode = mode + print_replacement = replacement + else: + print('* DePythonTeX error:') + print(' Improper typesetting information for file information on line ' + str(depy_linenum)) + print(' The file information was "' + nextdepytxline + '"') + sys.exit(1) + # Increment the line in depytx to check for more information + # from LaTeX + scan_ahead_line += 1 + if scan_ahead_line == len(depytx): + break + else: + nextdepytxline = depytx[scan_ahead_line] + + + # If the line we're looking for is within the range currently held by + # texcontent, do nothing. Otherwise, transfer content from tex + # to texout until we get to the line of tex that we're looking for + if depy_linenum > texlinenum: + texout.append(texcontent) + texlinenum += 1 + while texlinenum < depy_linenum: + texout.append(tex[texlinenum]) + texlinenum += 1 + texcontent = tex[texlinenum] + + + # Deal with arguments + # All arguments are parsed and stored in a list variables, even if + # they are not used, for completeness; this makes it easy to add + # functionality + # Start by splitting the current line into what comes before the + # command or environment, and what is after it + if depy_type == 'cmd': + try: + before, after = texcontent.split('\\' + depy_name, 1) + except: + print('* DePythonTeX error:') + print(' Could not find command "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: # depy_type == 'env': + try: + before, after = texcontent.split(r'\begin{' + depy_name + '}', 1) + except: + print('* DePythonTeX error:') + print(' Could not find environment "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # We won't need the content from before the command or environment + # again, so we go ahead and store it + texout.append(before) + + # Parse the arguments + # Create a list for storing the recovered arguments + arglist = list() + for argindex, arg in enumerate(depy_args): + if arg == 'n': + pass + elif arg == 'o': + if after[0] == '[': + # Account for possible line breaks before end of arg + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after[1:].split(']', 1) + else: + if obeylines: + # Take into account possible whitespace before arg + if bool(match('[ \t]*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # If this is the last arg, and it wasn't found, + # the macro should eat all whitespace following it + if argindex == len(depy_args) - 1: + after = sub('^[ \t]*', '', after) + else: + # Allow peeking ahead a line for the argument + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + # Take into account possible whitespace before arg + if bool(match('\s*\[', after)): + after = after.split('[', 1)[1] + while ']' not in after: + texlinenum += 1 + after += tex[texlinenum] + optarg, after = after.split(']', 1) + else: + optarg = None + # Account for eating whitespace afterward, if arg not found + if argindex == len(depy_args) - 1: + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if not bool(match('\s*$', after)): + after = sub('^\s*', '', after) + arglist.append(optarg) + elif arg == 'm': + # Account for possible line breaks or spaces before arg + if after[0] == '{': + after = after[1:] + else: + if obeylines: + # Account for possible leading whitespace + if bool(match('[ \t\f\v]*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + else: + # Peek ahead a line if needed + if bool(match('\s*$', after)) and after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + if bool(match('\s*\{', after)): + after = after.split('{', 1)[1] + else: + print('* DePythonTeX error:') + print(' Flawed mandatory argument for "' + depy_name + '" on line ' + str(depy_linenum)) + sys.exit(1) + # Go through the argument character by character to find the + # closing brace. + # If possible, use a very simple approach + if (r'\{' not in after and r'\}' not in after and + r'\string' not in after and + after.count('{') + 1 == after.count('}')): + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + # If a simple parsing approach won't work, parse in much + # greater depth + else: + pos = 0 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + # If the current character is a brace, we count it + lbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos] == '}': + # If the current character is a brace, we count it + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + elif after[pos:].startswith(r'\string'): + # If the current position marks the beginning of `\string`, we + # resolve the `\string` command + # First, jump ahead to after `\string` + pos += 7 #+= len(r'\string') + # See if `\string` is followed by a regular macro + # If so, jump past it; otherwise, figure out if a + # single-character macro, or just a single character, is next, + # and jump past it + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + elif line[pos] == '\\': + pos += 2 + else: + pos += 1 + elif line[pos] == '\\': + # If the current position is a backslash, figure out what + # macro is used, and jump past it + # The macro must either be a standard alphabetic macro, + # or a single-character macro + standard_macro = match(r'\\[a-zA-Z]+', line[pos:]) + if bool(standard_macro): + pos += standard_macro.end() + else: + pos += 2 + else: + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[:pos] + after = after[pos+1:] + arglist.append(mainarg) + elif arg == 'v': + if after[0] == '{': + # Account for the possibility of matched brace delimiters + # Not all verbatim commands allow for these + pos = 1 + lbraces = 1 + rbraces = 0 + while True: + if after[pos] == '{': + lbraces += 1 + elif after[pos] == '}': + rbraces += 1 + if lbraces == rbraces: + break + pos += 1 + if pos == len(after): + texlinenum += 1 + after += tex[texlinenum] + mainarg = after[1:pos] + after = after[pos+1:] + else: + # Deal with matched character delims + delim = after[0] + while after.count(delim) < 2: + texlinenum += 1 + after += tex[texlinenum] + mainarg, after = after[1:].split(delim, 1) + arglist.append(mainarg) + + + # Do substitution, depending on what is required + # Need a variable for processed content to be added to texout + processed = None + if depy_typeset == 'c': + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_cmd(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if code_replacement is None: + if end_environment not in after: + while True: + texlinenum += 1 + after += tex[texlinenum] + if end_environment in tex[texlinenum]: + break + code_replacement, after = after.split(end_environment, 1) + # If there's content on the line with the end-environment + # command, it should be discarded, to imitate TeX + if not code_replacement.endswith('\n'): + code_replacement = code_replacement.rsplit('\n', 1)[0] + '\n' + # Take care of `gobble` + if settings['gobble'] == 'auto': + code_replacement = textwrap.dedent(code_replacement) + else: + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_code_env(depy_name, arglist, + depy_linenum, + code_replacement, + code_replacement_mode, + after, depy_lexer, + firstnumber) + elif depy_typeset == 'p' and print_replacement is not None: + if depy_type == 'cmd': + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_cmd(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + # Make sure that `after` contains two lines of content + # This is needed for some replacements that need to look ahead + if after.count('\n') < 2: + texlinenum += 1 + after += tex[texlinenum] + processed, texcontent = replace_print_env(depy_name, arglist, + depy_linenum, + print_replacement, + print_replacement_mode, + source, + after) + else: # depy_typeset == 'n' or (depy_typeset == 'p' and print_replacement is None): + if depy_type == 'cmd': + texcontent = after + else: # depy_type == 'env' + end_environment = r'\end{' + depy_name + '}' + if end_environment not in after: + while True: + texlinenum += 1 + if end_environment in tex[texlinenum]: + after = tex[texlinenum] + break + after = after.split(end_environment, 1)[1] + if bool(match('\s*\n', after)): + # If the line following `after` is whitespace, it should + # be stripped, since most environments throw away + # anything after the end of the environment + after = after.split('\n')[1] + texcontent = after + # #### Once it's supported on the TeX side, need to add support for + # pc and cp + + + # Store any processed content + if processed is not None: + texout.append(processed) + + +# Transfer anything that's left in tex to texout +texout.append(texcontent) +texout.extend(tex[texlinenum+1:]) + + + + +# Replace the `\usepackage{pythontex}` +for n, line in enumerate(texout): + if '{pythontex}' in line: + startline = n + while '\\usepackage' not in texout[startline] and startline >= 0: + startline -= 1 + if startline == n: + if bool(search(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', line)): + texout[n] = sub(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', '', line) + break + else: + content = ''.join(texout[startline:n+1]) + if bool(search(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', content)): + replacement = sub(r'(?s)\\usepackage(?:\[.*?\]\s*){0,1}\{pythontex\}', '', content) + texout[startline] = replacement + for l in range(startline+1, n+1): + texout[l] = '' + break + elif line.startswith(r'\begin{document}'): + break +if preamble_additions: + texout[n] += '\n'.join(preamble_additions) + '\n' +# Take care of graphicspath +if args.graphicspath and settings['graphicx']: + for n, line in enumerate(texout): + if '\\graphicspath' in line and not bool(match('\s*%', line)): + texout[n] = line.replace('\\graphicspath{', '\\graphicspath{{' + settings['outputdir'] +'/}') + break + elif line.startswith(r'\begin{document}'): + texout[n] = '\\graphicspath{{' + settings['outputdir'] + '/}}\n' + line + break + + + + +# Print any final messages +if forced_double_space_list: + print('* DePythonTeX warning:') + print(' A trailing double space was forced with "\\space{}" for the following') + print(' This can happen when printed content is included inline') + print(' The forced double space is only an issue if it is not intentional') + for name, linenum in forced_double_space_list: + print(' "' + name + '" near line ' + str(linenum)) + + + + +# Write output +for line in texout: + outfile.write(line) +outfile.close() diff --git a/Master/texmf-dist/scripts/pythontex/pythontex.py b/Master/texmf-dist/scripts/pythontex/pythontex.py new file mode 100755 index 00000000000..1be1f6d70ea --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +This is the PythonTeX wrapper script. It automatically detects the version +of Python, and then imports the correct code from pythontex2.py or +pythontex3.py. + +Licensed under the BSD 3-Clause License: + +Copyright (c) 2012-2013, Geoffrey M. Poore + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' + + +import sys +if sys.version_info.major == 2: + if sys.version_info.minor >= 7: + import pythontex2 as pythontex + else: + sys.exit('PythonTeX require Python 2.7; you are using 2.{0}'.format(sys.version_info.minor)) +elif sys.version_info.major == 3: + if sys.version_info.minor >= 2: + import pythontex3 as pythontex + else: + sys.exit('PythonTeX require Python 3.2+; you are using 3.{0}'.format(sys.version_info.minor)) + +# The "if" statement is needed for multiprocessing under Windows; see the +# multiprocessing documentation. +if __name__ == '__main__': + pythontex.main() diff --git a/Master/texmf-dist/scripts/pythontex/pythontex2.py b/Master/texmf-dist/scripts/pythontex/pythontex2.py new file mode 100755 index 00000000000..c123adee22b --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex2.py @@ -0,0 +1,2360 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +This is the main PythonTeX script. It should be launched via pythontex.py. + +Two versions of this script are provided. One, with name ending in "2", runs +under Python 2.7. The other, with name ending in "3", runs under Python 3.2+. + +This script needs to be able to import pythontex_engines.py; in general it +should be in the same directory. + + +Licensed under the BSD 3-Clause License: + +Copyright (c) 2012-2013, Geoffrey M. Poore + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' + + +# Imports +#// Python 2 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +#\\ End Python 2 +import sys +import os +import argparse +import codecs +import time +from hashlib import sha1 +from collections import defaultdict, OrderedDict, namedtuple +from re import match, sub, search +import subprocess +import multiprocessing +from pygments.styles import get_all_styles +from pythontex_engines import * +import textwrap + +if sys.version_info[0] == 2: + try: + import cPickle as pickle + except: + import pickle + from io import open +else: + import pickle + + + + +# Script parameters +# Version +version = 'v0.12' + + + + +class Pytxcode(object): + def __init__(self, data, gobble): + self.delims, self.code = data.split('#\n', 1) + self.input_family, self.input_session, self.input_restart, self.input_instance, self.input_command, self.input_context, self.input_args_run, self.input_args_prettyprint, self.input_file, self.input_line = self.delims.split('#') + self.input_instance_int = int(self.input_instance) + self.input_line_int = int(self.input_line) + self.key_run = self.input_family + '#' + self.input_session + '#' + self.input_restart + self.key_typeset = self.key_run + '#' + self.input_instance + self.hashable_delims_run = self.key_typeset + '#' + self.input_command + '#' + self.input_context + '#' + self.input_args_run + self.hashable_delims_typeset = self.key_typeset + '#' + self.input_command + '#' + self.input_context + '#' + self.input_args_run + if len(self.input_command) > 1: + self.is_inline = False + # Environments start on the next line + self.input_line_int += 1 + self.input_line = str(self.input_line_int) + else: + self.is_inline = True + self.is_extfile = True if self.input_session.startswith('EXT:') else False + if self.is_extfile: + self.extfile = os.path.expanduser(os.path.normcase(self.input_session.replace('EXT:', '', 1))) + self.is_cc = True if self.input_family.startswith('CC:') else False + self.is_pyg = True if self.input_family.startswith('PYG') else False + self.is_verb = True if self.input_restart.endswith('verb') else False + if self.is_cc: + self.input_instance += 'CC' + self.cc_type, self.cc_pos = self.input_family.split(':')[1:] + if self.is_verb or self.is_pyg or self.is_cc: + self.is_cons = False + else: + self.is_cons = engine_dict[self.input_family].console + self.is_code = False if self.is_verb or self.is_pyg or self.is_cc or self.is_cons else True + if self.input_command in ('c', 'code') or (self.input_command == 'i' and not self.is_cons): + self.is_typeset = False + else: + self.is_typeset = True + + if gobble == 'auto': + self.code = textwrap.dedent(self.code) + + + + +def process_argv(data, temp_data): + ''' + Process command line options using the argparse module. + + Most options are passed via the file of code, rather than via the command + line. + ''' + + # Create a command line argument parser + parser = argparse.ArgumentParser() + parser.add_argument('TEXNAME', + help='LaTeX file, with or without .tex extension') + parser.add_argument('--version', action='version', + version='PythonTeX {0}'.format(data['version'])) + parser.add_argument('--encoding', default='UTF-8', + help='encoding for all text files (see codecs module for encodings)') + parser.add_argument('--error-exit-code', default='true', + choices=('true', 'false'), + help='return exit code of 1 if there are errors (not desirable with some TeX editors and workflows)') + group = parser.add_mutually_exclusive_group() + group.add_argument('--runall', nargs='?', default='false', + const='true', choices=('true', 'false'), + help='run ALL code; equivalent to package option') + group.add_argument('--rerun', default='errors', + choices=('never', 'modified', 'errors', 'warnings', 'always'), + help='set conditions for rerunning code; equivalent to package option') + parser.add_argument('--hashdependencies', nargs='?', default='false', + const='true', choices=('true', 'false'), + help='hash dependencies (such as external data) to check for modification, rather than using mtime; equivalent to package option') + parser.add_argument('-v', '--verbose', default=False, action='store_true', + help='verbose output') + parser.add_argument('--interpreter', default=None, help='set a custom interpreter; argument should be in the form "<interpreter>:<command>, <interp>:<cmd>, ..." where <interpreter> is "python", "ruby", etc., and <command> is the command for invoking the interpreter; argument may also be in the form of a Python dictionary') + args = parser.parse_args() + + # Store the parsed argv in data and temp_data + data['encoding'] = args.encoding + if args.error_exit_code == 'true': + temp_data['error_exit_code'] = True + else: + temp_data['error_exit_code'] = False + # runall can be mapped onto rerun, so both are stored under rerun + if args.runall == 'true': + temp_data['rerun'] = 'always' + else: + temp_data['rerun'] = args.rerun + # hashdependencies need only be in temp_data, since changing it would + # change hashes (hashes of mtime vs. file contents) + if args.hashdependencies == 'true': + temp_data['hashdependencies'] = True + else: + temp_data['hashdependencies'] = False + temp_data['verbose'] = args.verbose + # Update interpreter_dict based on interpreter + if args.interpreter is not None: + interp_list = args.interpreter.lstrip('{').rstrip('}').split(',') + for interp in interp_list: + if interp: + try: + k, v = interp.split(':') + k = k.strip(' \'"') + v = v.strip(' \'"') + interpreter_dict[k] = v + except: + print('Invalid --interpreter argument') + return sys.exit(2) + + + if args.TEXNAME is not None: + # Determine if we a dealing with just a filename, or a name plus + # path. If there's a path, we need to make the document directory + # the current working directory. + dir, raw_jobname = os.path.split(args.TEXNAME) + dir = os.path.expanduser(os.path.normcase(dir)) + if dir: + os.chdir(dir) + sys.path.append(dir) + # If necessary, strip off an extension to find the raw jobname that + # corresponds to the .pytxcode. + if not os.path.exists(raw_jobname + '.pytxcode'): + raw_jobname = raw_jobname.rsplit('.', 1)[0] + if not os.path.exists(raw_jobname + '.pytxcode'): + print('* PythonTeX error') + print(' Code file ' + raw_jobname + '.pytxcode does not exist.') + print(' Run LaTeX to create it.') + return sys.exit(1) + + # We need a "sanitized" version of the jobname, with spaces and + # asterisks replaced with hyphens. This is done to avoid TeX issues + # with spaces in file names, paralleling the approach taken in + # pythontex.sty. From now on, we will use the sanitized version every + # time we create a file that contains the jobname string. The raw + # version will only be used in reference to pre-existing files created + # on the TeX side, such as the .pytxcode file. + jobname = raw_jobname.replace(' ', '-').replace('"', '').replace('*', '-') + # Store the results in data + data['raw_jobname'] = raw_jobname + data['jobname'] = jobname + + # We need to check to make sure that the "sanitized" jobname doesn't + # lead to a collision with a file that already has that name, so that + # two files attempt to use the same PythonTeX folder. + # + # If <jobname>.<ext> and <raw_jobname>.<ext> both exist, where <ext> + # is a common LaTeX extension, we exit. We operate under the + # assumption that there should be only a single file <jobname> in the + # document root directory that has a common LaTeX extension. That + # could be false, but if so, the user probably has worse things to + # worry about than a potential PythonTeX output collision. + # If <jobname>* and <raw_jobname>* both exist, we issue a warning but + # attempt to proceed. + if jobname != raw_jobname: + resolved = False + for ext in ('.tex', '.ltx', '.dtx'): + if os.path.isfile(raw_jobname + ext): + if os.path.isfile(jobname + ext): + print('* PythonTeX error') + print(' Directory naming collision between the following files:') + print(' ' + raw_jobname + ext) + print(' ' + jobname + ext) + return sys.exit(1) + else: + resolved = True + break + if not resolved: + ls = os.listdir('.') + for file in ls: + if file.startswith(jobname): + print('* PythonTeX warning') + print(' Potential directory naming collision between the following names:') + print(' ' + raw_jobname) + print(' ' + jobname + '*') + print(' Attempting to proceed.') + temp_data['warnings'] += 1 + break + + + + +def load_code_get_settings(data, temp_data): + ''' + Load the code file, preprocess the code, and extract the settings. + ''' + # Bring in the .pytxcode file as a single string + raw_jobname = data['raw_jobname'] + encoding = data['encoding'] + # The error checking here is a little redundant + if os.path.isfile(raw_jobname + '.pytxcode'): + f = open(raw_jobname + '.pytxcode', 'r', encoding=encoding) + pytxcode = f.read() + f.close() + else: + print('* PythonTeX error') + print(' Code file ' + raw_jobname + '.pytxcode does not exist.') + print(' Run LaTeX to create it.') + return sys.exit(1) + + # Split code and settings + try: + pytxcode, pytxsettings = pytxcode.rsplit('=>PYTHONTEX:SETTINGS#', 1) + except: + print('The .pytxcode file appears to have an outdated format or be invalid') + print('Run LaTeX to make sure the file is current') + return sys.exit(1) + + + # Prepare to process settings + # + # Create a dict for storing settings. + settings = {} + # Create a dict for storing Pygments settings. + # Each dict entry will itself be a dict. + pygments_settings = defaultdict(dict) + + # Create a dict of processing functions, and generic processing functions + settings_func = dict() + def set_kv_data(k, v): + if v == 'true': + settings[k] = True + elif v == 'false': + settings[k] = False + else: + settings[k] = v + # Need a function for when assignment is only needed if not default value + def set_kv_temp_data_if_not_default(k, v): + if v != 'default': + if v == 'true': + temp_data[k] = True + elif v == 'false': + temp_data[k] = False + else: + temp_data[k] = v + def set_kv_data_fvextfile(k, v): + # Error checking on TeX side should be enough, but be careful anyway + try: + v = int(v) + except ValueError: + print('* PythonTeX error') + print(' Unable to parse package option fvextfile.') + return sys.exit(1) + if v < 0: + settings[k] = sys.maxsize + elif v == 0: + settings[k] = 1 + print('* PythonTeX warning') + print(' Invalid value for package option fvextfile.') + temp_data['warnings'] += 1 + else: + settings[k] = v + def set_kv_pygments(k, v): + input_family, lexer_opts, options = v.replace(' ','').split('|') + lexer = None + lex_dict = {} + opt_dict = {} + if lexer_opts: + for l in lexer_opts.split(','): + if '=' in l: + k, v = l.split('=', 1) + if k == 'lexer': + lexer = l + else: + lex_dict[k] = v + else: + lexer = l + if options: + for o in options.split(','): + if '=' in o: + k, v = o.split('=', 1) + if v in ('true', 'True'): + v = True + elif v in ('false', 'False'): + v = False + else: + k = option + v = True + opt_dict[k] = v + if input_family != ':GLOBAL': + if 'lexer' in pygments_settings[':GLOBAL']: + lexer = pygments_settings[':GLOBAL']['lexer'] + lex_dict.update(pygments_settings[':GLOBAL']['lexer_options']) + opt_dict.update(pygments_settings[':GLOBAL']['formatter_options']) + if 'style' not in opt_dict: + opt_dict['style'] = 'default' + opt_dict['commandprefix'] = 'PYG' + opt_dict['style'] + if lexer is not None: + pygments_settings[input_family]['lexer'] = lexer + pygments_settings[input_family]['lexer_options'] = lex_dict + pygments_settings[input_family]['formatter_options'] = opt_dict + settings_func['version'] = set_kv_data + settings_func['outputdir'] = set_kv_data + settings_func['workingdir'] = set_kv_data + settings_func['gobble'] = set_kv_data + settings_func['rerun'] = set_kv_temp_data_if_not_default + settings_func['hashdependencies'] = set_kv_temp_data_if_not_default + settings_func['makestderr'] = set_kv_data + settings_func['stderrfilename'] = set_kv_data + settings_func['keeptemps'] = set_kv_data + settings_func['pyfuture'] = set_kv_data + settings_func['pyconfuture'] = set_kv_data + settings_func['pygments'] = set_kv_data + settings_func['fvextfile'] = set_kv_data_fvextfile + settings_func['pygglobal'] = set_kv_pygments + settings_func['pygfamily'] = set_kv_pygments + settings_func['pyconbanner'] = set_kv_data + settings_func['pyconfilename'] = set_kv_data + settings_func['depythontex'] = set_kv_data + + # Process settings + for line in pytxsettings.split('\n'): + if line: + key, val = line.split('=', 1) + try: + settings_func[key](key, val) + except KeyError: + print('* PythonTeX warning') + print(' Unknown option "' + key + '"') + temp_data['warnings'] += 1 + + # Check for compatility between the .pytxcode and the script + if 'version' not in settings or settings['version'] != data['version']: + print('* PythonTeX warning') + print(' The version of the PythonTeX scripts does not match') + print(' the last code saved by the document--run LaTeX to create') + print(' an updated version. Attempting to proceed.') + sys.stdout.flush() + + # Store all results that haven't already been stored. + data['settings'] = settings + data['pygments_settings'] = pygments_settings + + # Create a tuple of vital quantities that invalidate old saved data + # Don't need to include outputdir, because if that changes, no old output + # fvextfile could be checked on a case-by-case basis, which would result + # in faster output, but that would involve a good bit of additional + # logic, which probably isn't worth it for a feature that will rarely be + # changed. + data['vitals'] = (data['version'], data['encoding'], + settings['gobble'], settings['fvextfile']) + + # Create tuples of vital quantities + data['code_vitals'] = (settings['workingdir'], settings['keeptemps'], + settings['makestderr'], settings['stderrfilename']) + data['cons_vitals'] = (settings['workingdir']) + data['typeset_vitals'] = () + + # Pass any customizations to types + for k in engine_dict: + engine_dict[k].customize(pyfuture=settings['pyfuture'], + pyconfuture=settings['pyconfuture'], + pyconbanner=settings['pyconbanner'], + pyconfilename=settings['pyconfilename']) + + # Store code + # Do this last, so that Pygments settings are available + if pytxcode.startswith('=>PYTHONTEX#'): + gobble = settings['gobble'] + temp_data['pytxcode'] = [Pytxcode(c, gobble) for c in pytxcode.split('=>PYTHONTEX#')[1:]] + else: + temp_data['pytxcode'] = [] + + + + +def get_old_data(data, old_data, temp_data): + ''' + Load data from the last run, if it exists, into the dict old_data. + Determine the path to the PythonTeX scripts, either by using a previously + found, saved path or via kpsewhich. + + The old data is used for determining when PythonTeX has been upgraded, + when any settings have changed, when code has changed (via hashes), and + what files may need to be cleaned up. The location of the PythonTeX + scripts is needed so that they can be imported by the scripts created by + PythonTeX. The location of the scripts is confirmed even if they were + previously located, to make sure that the path is still valid. Finding + the scripts depends on having a TeX installation that includes the + Kpathsea library (TeX Live and MiKTeX, possibly others). + + All code that relies on old_data is written based on the assumption that + if old_data exists and has the current PythonTeX version, then it + contains all needed information. Thus, all code relying on old_data must + check that it was loaded and that it has the current version. If not, + code should adapt gracefully. + ''' + + # Create a string containing the name of the data file + pythontex_data_file = os.path.join(data['settings']['outputdir'], 'pythontex_data.pkl') + + # Load the old data if it exists (read as binary pickle) + if os.path.isfile(pythontex_data_file): + f = open(pythontex_data_file, 'rb') + old = pickle.load(f) + f.close() + # Check for compabilility + if 'vitals' in old and data['vitals'] == old['vitals']: + temp_data['loaded_old_data'] = True + old_data.update(old) + else: + temp_data['loaded_old_data'] = False + # Clean up all old files + if 'files' in old: + for key in old['files']: + for f in old['files'][key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + if 'pygments_files' in old: + for key in old['pygments_files']: + for f in old['pygments_files'][key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + else: + temp_data['loaded_old_data'] = False + + # Set the utilspath + if os.path.isfile(os.path.join(sys.path[0], 'pythontex_utils.py')): + # Need the path with forward slashes, so escaping isn't necessary + data['utilspath'] = sys.path[0].replace('\\', '/') + else: + print('* PythonTeX error') + print(' Could not determine the utils path from sys.path[0]') + print(' The file "pythontex_utils.py" may be missing') + return sys.exit(1) + + + + +def modified_dependencies(key, data, old_data, temp_data): + hashdependencies = temp_data['hashdependencies'] + if key not in old_data['dependencies']: + return False + else: + old_dep_hash_dict = old_data['dependencies'][key] + workingdir = data['settings']['workingdir'] + for dep in old_dep_hash_dict.keys(): + # We need to know if the path is relative (based off the + # working directory) or absolute. We can't use + # os.path.isabs() alone for determining the distinction, + # because we must take into account the possibility of an + # initial ~ (tilde) standing for the home directory. + dep_file = os.path.expanduser(os.path.normcase(dep)) + if not os.path.isabs(dep_file): + dep_file = os.path.join(workingdir, dep_file) + if not os.path.isfile(dep_file): + print('* PythonTeX error') + print(' Cannot find dependency "' + dep + '"') + print(' It belongs to ' + key.replace('#', ':')) + print(' Relative paths to dependencies must be specified from the working directory.') + temp_data['errors'] += 1 + # A removed dependency should trigger an error, but it + # shouldn't cause code to execute. Running the code + # again would just give more errors when it can't find + # the dependency. (There won't be issues when a + # dependency is added or removed, because that would + # involve modifying code, which would trigger + # re-execution.) + elif hashdependencies: + # Read and hash the file in binary. Opening in text mode + # would require an unnecessary decoding and encoding cycle. + f = open(dep_file, 'rb') + hasher = sha1() + hash = hasher(f.read()).hexdigest() + f.close() + if hash != old_dep_hash_dict[dep][1]: + return True + else: + mtime = os.path.getmtime(dep_file) + if mtime != old_dep_hash_dict[dep][0]: + return True + return False + +def should_rerun(hash, old_hash, old_exit_status, key, rerun, data, old_data, temp_data): + # #### Need to clean up arg passing here + if rerun == 'never': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data)): + print('* PythonTeX warning') + print(' Session ' + key.replace('#', ':') + ' has rerun=never') + print(' But its code or dependencies have been modified') + temp_data['warnings'] += 1 + return False + elif rerun == 'modified': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data)): + return True + else: + return False + elif rerun == 'errors': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data) or + old_exit_status[0] != 0): + return True + else: + return False + elif rerun == 'warnings': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data) or + old_exit_status != (0, 0)): + return True + else: + return False + elif rerun == 'always': + return True + + + + +def hash_all(data, temp_data, old_data, engine_dict): + ''' + Hash the code to see what has changed and needs to be updated. + + Save the hashes in hashdict. Create update_code, a list of bools + regarding whether code should be executed. Create update_pygments, a + list of bools determining what needs updated Pygments highlighting. + Update pygments_settings to account for Pygments (as opposed to PythonTeX) + commands and environments. + ''' + + # Note that the PythonTeX information that accompanies code must be + # hashed in addition to the code itself; the code could stay the same, + # but its context or args could change, which might require that code be + # executed. All of the PythonTeX information is hashed except for the + # input line number. Context-dependent code is going too far if + # it depends on that. + + # Create variables to more easily access parts of data + pytxcode = temp_data['pytxcode'] + encoding = data['encoding'] + loaded_old_data = temp_data['loaded_old_data'] + rerun = temp_data['rerun'] + pygments_settings = data['pygments_settings'] + + # Calculate cumulative hashes for all code that is executed + # Calculate individual hashes for all code that will be typeset + code_hasher = defaultdict(sha1) + cons_hasher = defaultdict(sha1) + cc_hasher = defaultdict(sha1) + typeset_hasher = defaultdict(sha1) + for c in pytxcode: + if c.is_code: + code_hasher[c.key_run].update(c.hashable_delims_run.encode(encoding)) + code_encoded = c.code.encode(encoding) + code_hasher[c.key_run].update(code_encoded) + if c.is_typeset: + typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding)) + typeset_hasher[c.key_typeset].update(code_encoded) + elif c.is_cons: + cons_hasher[c.key_run].update(c.hashable_delims_run.encode(encoding)) + code_encoded = c.code.encode(encoding) + cons_hasher[c.key_run].update(code_encoded) + if c.is_typeset: + typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding)) + typeset_hasher[c.key_typeset].update(code_encoded) + elif c.is_cc: + cc_hasher[c.cc_type].update(c.hashable_delims_run.encode(encoding)) + cc_hasher[c.cc_type].update(c.code.encode(encoding)) + elif c.is_typeset: + typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding)) + typeset_hasher[c.key_typeset].update(c.code.encode(encoding)) + + + # Store hashes + code_hash_dict = {} + for key in code_hasher: + input_family = key.split('#', 1)[0] + code_hash_dict[key] = (code_hasher[key].hexdigest(), + cc_hasher[input_family].hexdigest(), + engine_dict[input_family].get_hash()) + data['code_hash_dict'] = code_hash_dict + + cons_hash_dict = {} + for key in cons_hasher: + input_family = key.split('#', 1)[0] + cons_hash_dict[key] = (cons_hasher[key].hexdigest(), + cc_hasher[input_family].hexdigest(), + engine_dict[input_family].get_hash()) + data['cons_hash_dict'] = cons_hash_dict + + typeset_hash_dict = {} + for key in typeset_hasher: + typeset_hash_dict[key] = typeset_hasher[key].hexdigest() + data['typeset_hash_dict'] = typeset_hash_dict + + + # See what needs to be updated. + # In the process, copy over macros and files that may be reused. + code_update = {} + cons_update = {} + pygments_update = {} + macros = defaultdict(list) + files = defaultdict(list) + pygments_macros = {} + pygments_files = {} + typeset_cache = {} + dependencies = defaultdict(dict) + exit_status = {} + pygments_settings_changed = {} + if loaded_old_data: + old_macros = old_data['macros'] + old_files = old_data['files'] + old_pygments_macros = old_data['pygments_macros'] + old_pygments_files = old_data['pygments_files'] + old_typeset_cache = old_data['typeset_cache'] + old_dependencies = old_data['dependencies'] + old_exit_status = old_data['exit_status'] + old_code_hash_dict = old_data['code_hash_dict'] + old_cons_hash_dict = old_data['cons_hash_dict'] + old_typeset_hash_dict = old_data['typeset_hash_dict'] + old_pygments_settings = old_data['pygments_settings'] + for s in pygments_settings: + if (s in old_pygments_settings and + pygments_settings[s] == old_pygments_settings[s]): + pygments_settings_changed[s] = False + else: + pygments_settings_changed[s] = True + + # If old data was loaded (and thus is compatible) determine what has + # changed so that only + # modified code may be executed. Otherwise, execute everything. + # We don't have to worry about checking for changes in pyfuture, because + # custom code and default code are hashed. The treatment of keeptemps + # could be made more efficient (if changed to 'none', just delete old temp + # files rather than running everything again), but given that it is + # intended as a debugging aid, that probable isn't worth it. + # We don't have to worry about hashdependencies changing, because if it + # does the hashes won't match (file contents vs. mtime) and thus code will + # be re-executed. + if loaded_old_data and data['code_vitals'] == old_data['code_vitals']: + # Compare the hash values, and set which code needs to be run + for key in code_hash_dict: + if (key in old_code_hash_dict and + not should_rerun(code_hash_dict[key], old_code_hash_dict[key], old_exit_status[key], key, rerun, data, old_data, temp_data)): + code_update[key] = False + macros[key] = old_macros[key] + files[key] = old_files[key] + dependencies[key] = old_dependencies[key] + exit_status[key] = old_exit_status[key] + else: + code_update[key] = True + else: + for key in code_hash_dict: + code_update[key] = True + + if loaded_old_data and data['cons_vitals'] == old_data['cons_vitals']: + # Compare the hash values, and set which code needs to be run + for key in cons_hash_dict: + if (key in old_cons_hash_dict and + not should_rerun(cons_hash_dict[key], old_cons_hash_dict[key], old_exit_status[key], key, rerun, data, old_data, temp_data)): + cons_update[key] = False + macros[key] = old_macros[key] + files[key] = old_files[key] + typeset_cache[key] = old_typeset_cache[key] + dependencies[key] = old_dependencies[key] + exit_status[key] = old_exit_status[key] + else: + cons_update[key] = True + else: + for key in cons_hash_dict: + cons_update[key] = True + + if loaded_old_data and data['typeset_vitals'] == old_data['typeset_vitals']: + for key in typeset_hash_dict: + input_family = key.split('#', 1)[0] + if input_family in pygments_settings: + if (not pygments_settings_changed[input_family] and + key in old_typeset_hash_dict and + typeset_hash_dict[key] == old_typeset_hash_dict[key]): + pygments_update[key] = False + if key in old_pygments_macros: + pygments_macros[key] = old_pygments_macros[key] + if key in old_pygments_files: + pygments_files[key] = old_pygments_files[key] + else: + pygments_update[key] = True + else: + pygments_update[key] = False + # Make sure Pygments styles are up-to-date + pygments_style_list = list(get_all_styles()) + if pygments_style_list != old_data['pygments_style_list']: + pygments_style_defs = {} + # Lazy import + from pygments.formatters import LatexFormatter + for s in pygments_style_list: + formatter = LatexFormatter(style=s, commandprefix='PYG'+s) + pygments_style_defs[s] = formatter.get_style_defs() + else: + pygments_style_defs = old_data['pygments_style_defs'] + else: + for key in typeset_hash_dict: + input_family = key.split('#', 1)[0] + if input_family in pygments_settings: + pygments_update[key] = True + else: + pygments_update[key] = False + # Create Pygments styles + pygments_style_list = list(get_all_styles()) + pygments_style_defs = {} + # Lazy import + from pygments.formatters import LatexFormatter + for s in pygments_style_list: + formatter = LatexFormatter(style=s, commandprefix='PYG'+s) + pygments_style_defs[s] = formatter.get_style_defs() + + # Save to data + temp_data['code_update'] = code_update + temp_data['cons_update'] = cons_update + temp_data['pygments_update'] = pygments_update + data['macros'] = macros + data['files'] = files + data['pygments_macros'] = pygments_macros + data['pygments_style_list'] = pygments_style_list + data['pygments_style_defs'] = pygments_style_defs + data['pygments_files'] = pygments_files + data['typeset_cache'] = typeset_cache + data['dependencies'] = dependencies + data['exit_status'] = exit_status + + + # Clean up for code that will be run again, and for code that no longer + # exists. + if loaded_old_data: + # Take care of code files + for key in code_hash_dict: + if code_update[key] and key in old_files: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + for key in old_code_hash_dict: + if key not in code_hash_dict: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + # Take care of old console files + for key in cons_hash_dict: + if cons_update[key] and key in old_files: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + for key in old_cons_hash_dict: + if key not in cons_hash_dict: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + # Take care of old Pygments files + # The approach here is a little different since there isn't a + # Pygments-specific hash dict, but there is a Pygments-specific + # dict of lists of files. + for key in pygments_update: + if pygments_update[key] and key in old_pygments_files: + for f in old_pygments_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + for key in old_pygments_files: + if key not in pygments_update: + for f in old_pygments_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + + + + + +def parse_code_write_scripts(data, temp_data, engine_dict): + ''' + Parse the code file into separate scripts, and write them to file. + ''' + code_dict = defaultdict(list) + cc_dict_begin = defaultdict(list) + cc_dict_end = defaultdict(list) + cons_dict = defaultdict(list) + pygments_list = [] + # Create variables to ease data access + encoding = data['encoding'] + utilspath = data['utilspath'] + outputdir = data['settings']['outputdir'] + workingdir = data['settings']['workingdir'] + pytxcode = temp_data['pytxcode'] + code_update = temp_data['code_update'] + cons_update = temp_data['cons_update'] + pygments_update = temp_data['pygments_update'] + files = data['files'] + # We need to keep track of the last instance for each session, so + # that duplicates can be eliminated. Some LaTeX environments process + # their content multiple times and thus will create duplicates. We + # need to initialize everything at -1, since instances begin at zero. + def negative_one(): + return -1 + last_instance = defaultdict(negative_one) + for c in pytxcode: + if c.input_instance_int > last_instance[c.key_run]: + last_instance[c.key_run] = c.input_instance_int + if c.is_code: + if code_update[c.key_run]: + code_dict[c.key_run].append(c) + if c.is_typeset and pygments_update[c.key_typeset]: + pygments_list.append(c) + elif c.is_cons: + # Only append to Pygments if not run, since Pygments is + # automatically taken care of during run for console content + if cons_update[c.key_run]: + cons_dict[c.key_run].append(c) + elif c.is_typeset and pygments_update[c.key_typeset]: + pygments_list.append(c) + elif (c.is_pyg or c.is_verb) and pygments_update[c.key_typeset]: + pygments_list.append(c) + elif c.is_cc: + if c.cc_pos == 'begin': + cc_dict_begin[c.cc_type].append(c) + else: + cc_dict_end[c.cc_type].append(c) + + # Save + temp_data['code_dict'] = code_dict + temp_data['cc_dict_begin'] = cc_dict_begin + temp_data['cc_dict_end'] = cc_dict_end + temp_data['cons_dict'] = cons_dict + temp_data['pygments_list'] = pygments_list + + # Save the code sessions that need to be updated + # Keep track of the files that are created + # Also accumulate error indices for handling stderr + code_index_dict = {} + for key in code_dict: + input_family, input_session, input_restart = key.split('#') + fname = os.path.join(outputdir, input_family + '_' + input_session + '_' + input_restart + '.' + engine_dict[input_family].extension) + files[key].append(fname) + sessionfile = open(fname, 'w', encoding=encoding) + script, code_index = engine_dict[input_family].get_script(encoding, + utilspath, + workingdir, + cc_dict_begin[input_family], + code_dict[key], + cc_dict_end[input_family]) + for lines in script: + sessionfile.write(lines) + sessionfile.close() + code_index_dict[key] = code_index + temp_data['code_index_dict'] = code_index_dict + + + + +def do_multiprocessing(data, temp_data, old_data, engine_dict): + jobname = data['jobname'] + encoding = data['encoding'] + outputdir = data['settings']['outputdir'] + workingdir = data['settings']['workingdir'] + keeptemps = data['settings']['keeptemps'] + fvextfile = data['settings']['fvextfile'] + pygments_settings = data['pygments_settings'] + verbose = temp_data['verbose'] + + code_dict = temp_data['code_dict'] + cons_dict = temp_data['cons_dict'] + cc_dict_begin = temp_data['cc_dict_begin'] + cc_dict_end = temp_data['cc_dict_end'] + pygments_list = temp_data['pygments_list'] + pygments_style_defs = data['pygments_style_defs'] + + files = data['files'] + macros = data['macros'] + pygments_files = data['pygments_files'] + pygments_macros = data['pygments_macros'] + typeset_cache = data['typeset_cache'] + + errors = temp_data['errors'] + warnings = temp_data['warnings'] + + makestderr = data['settings']['makestderr'] + stderrfilename = data['settings']['stderrfilename'] + code_index_dict = temp_data['code_index_dict'] + + hashdependencies = temp_data['hashdependencies'] + dependencies = data['dependencies'] + exit_status = data['exit_status'] + start_time = data['start_time'] + + + # Set maximum number of concurrent processes for multiprocessing + # Accoding to the docs, cpu_count() may raise an error + try: + max_processes = multiprocessing.cpu_count() + except NotImplementedError: + max_processes = 1 + pool = multiprocessing.Pool(max_processes) + tasks = [] + + # If verbose, print a list of processes + if verbose: + print('\n* PythonTeX will run the following processes:') + + # Add code processes. Note that everything placed in the codedict + # needs to be executed, based on previous testing, except for custom code. + for key in code_dict: + input_family = key.split('#')[0] + # Uncomment the following for debugging, and comment out what follows + '''run_code(encoding, outputdir, workingdir, code_dict[key], + engine_dict[input_family].language, + engine_dict[input_family].command, + engine_dict[input_family].created, + engine_dict[input_family].extension, + makestderr, stderrfilename, + code_index_dict[key], + engine_dict[input_family].errors, + engine_dict[input_family].warnings, + engine_dict[input_family].linenumbers, + engine_dict[input_family].lookbehind, + keeptemps, hashdependencies)''' + tasks.append(pool.apply_async(run_code, [encoding, outputdir, + workingdir, code_dict[key], + engine_dict[input_family].language, + engine_dict[input_family].command, + engine_dict[input_family].created, + engine_dict[input_family].extension, + makestderr, stderrfilename, + code_index_dict[key], + engine_dict[input_family].errors, + engine_dict[input_family].warnings, + engine_dict[input_family].linenumbers, + engine_dict[input_family].lookbehind, + keeptemps, hashdependencies])) + if verbose: + print(' - Code process ' + key.replace('#', ':')) + + # Add console processes + for key in cons_dict: + input_family = key.split('#')[0] + if engine_dict[input_family].language.startswith('python'): + if input_family in pygments_settings: + # Uncomment the following for debugging + '''python_console(jobname, encoding, outputdir, workingdir, + fvextfile, pygments_settings[input_family], + cc_dict_begin[input_family], cons_dict[key], + cc_dict_end[input_family], engine_dict[input_family].startup, + engine_dict[input_family].banner, + engine_dict[input_family].filename)''' + tasks.append(pool.apply_async(python_console, [jobname, encoding, + outputdir, workingdir, + fvextfile, + pygments_settings[input_family], + cc_dict_begin[input_family], + cons_dict[key], + cc_dict_end[input_family], + engine_dict[input_family].startup, + engine_dict[input_family].banner, + engine_dict[input_family].filename])) + else: + tasks.append(pool.apply_async(python_console, [jobname, encoding, + outputdir, workingdir, + fvextfile, + None, + cc_dict_begin[input_family], + cons_dict[key], + cc_dict_end[input_family], + engine_dict[input_family].startup, + engine_dict[input_family].banner, + engine_dict[input_family].filename])) + else: + print('* PythonTeX error') + print(' Currently, non-Python consoles are not supported') + errors += 1 + if verbose: + print(' - Console process ' + key.replace('#', ':')) + + # Add a Pygments process + if pygments_list: + tasks.append(pool.apply_async(do_pygments, [encoding, outputdir, + fvextfile, + pygments_list, + pygments_settings, + typeset_cache])) + if verbose: + print(' - Pygments process') + + # Execute the processes + pool.close() + pool.join() + + # Get the outputs of processes + # Get the files and macros created. Get the number of errors and warnings + # produced. Get any messages returned. Get the exit_status, which is a + # dictionary of code that failed and thus must be run again (its hash is + # set to a null string). Keep track of whether there were any new files, + # so that the last time of file creation in .pytxmcr can be updated. + new_files = False + messages = [] + for task in tasks: + result = task.get() + if result['process'] == 'code': + key = result['key'] + files[key].extend(result['files']) + if result['files']: + new_files = True + macros[key].extend(result['macros']) + dependencies[key] = result['dependencies'] + errors += result['errors'] + warnings += result['warnings'] + exit_status[key] = (result['errors'], result['warnings']) + messages.extend(result['messages']) + elif result['process'] == 'console': + key = result['key'] + files[key].extend(result['files']) + if result['files']: + new_files = True + macros[key].extend(result['macros']) + pygments_files.update(result['pygments_files']) + pygments_macros.update(result['pygments_macros']) + dependencies[key] = result['dependencies'] + typeset_cache[key] = result['typeset_cache'] + errors += result['errors'] + warnings += result['warnings'] + exit_status[key] = (result['errors'], result['warnings']) + messages.extend(result['messages']) + elif result['process'] == 'pygments': + pygments_files.update(result['pygments_files']) + for k in result['pygments_files']: + if result['pygments_files'][k]: + new_files = True + break + pygments_macros.update(result['pygments_macros']) + errors += result['errors'] + warnings += result['warnings'] + messages.extend(result['messages']) + + # Do a quick check to see if any dependencies were modified since the + # beginning of the run. If so, reset them so they will run next time and + # issue a warning + unresolved_dependencies = False + unresolved_sessions= [] + for key in dependencies: + for dep, val in dependencies[key].items(): + if val[0] > start_time: + unresolved_dependencies = True + dependencies[key][dep] = (None, None) + unresolved_sessions.append(key.replace('#', ':')) + if unresolved_dependencies: + print('* PythonTeX warning') + print(' The following have dependencies that have been modified') + print(' Run PythonTeX again to resolve dependencies') + for s in set(unresolved_sessions): + print(' - ' + s) + warnings += 1 + + + # Save all content (only needs to be done if code was indeed run). + # Save a commented-out time corresponding to the last time PythonTeX ran + # and created files, so that tools like latexmk can easily detect when + # another run is needed. + if tasks: + if new_files or not temp_data['loaded_old_data']: + last_new_file_time = start_time + else: + last_new_file_time = old_data['last_new_file_time'] + data['last_new_file_time'] = last_new_file_time + + macro_file = open(os.path.join(outputdir, jobname + '.pytxmcr'), 'w', encoding=encoding) + macro_file.write('%Last time of file creation: ' + str(last_new_file_time) + '\n\n') + for key in macros: + macro_file.write(''.join(macros[key])) + macro_file.close() + + pygments_macro_file = open(os.path.join(outputdir, jobname + '.pytxpyg'), 'w', encoding=encoding) + # Only save Pygments styles that are used + style_set = set([pygments_settings[k]['formatter_options']['style'] for k in pygments_settings if k != ':GLOBAL']) + for key in pygments_style_defs: + if key in style_set: + pygments_macro_file.write(''.join(pygments_style_defs[key])) + for key in pygments_macros: + pygments_macro_file.write(''.join(pygments_macros[key])) + pygments_macro_file.close() + + pythontex_data_file = os.path.join(outputdir, 'pythontex_data.pkl') + f = open(pythontex_data_file, 'wb') + pickle.dump(data, f, -1) + f.close() + + # Print any errors and warnings. + if messages: + print('\n'.join(messages)) + sys.stdout.flush() + # Store errors and warnings back into temp_data + # This is needed because they are ints and thus immutable + temp_data['errors'] = errors + temp_data['warnings'] = warnings + + + + +def run_code(encoding, outputdir, workingdir, code_list, language, command, + command_created, extension, makestderr, stderrfilename, + code_index, errorsig, warningsig, linesig, stderrlookbehind, + keeptemps, hashdependencies): + ''' + Function for multiprocessing code files + ''' + import shlex + + # Create what's needed for storing results + input_family = code_list[0].input_family + input_session = code_list[0].input_session + key_run = code_list[0].key_run + files = [] + macros = [] + dependencies = {} + errors = 0 + warnings = 0 + unknowns = 0 + messages = [] + + # Create message lists only for stderr, one for undelimited stderr and + # one for delimited, so it's easy to keep track of if there is any + # stderr. These are added onto messages at the end. + err_messages_ud = [] + err_messages_d = [] + + # We need to let the user know we are switching code files + # We check at the end to see if there were indeed any errors and warnings + # and if not, clear messages. + messages.append('\n---- Messages for ' + key_run.replace('#', ':') + ' ----') + + # Open files for stdout and stderr, run the code, then close the files + basename = key_run.replace('#', '_') + out_file_name = os.path.join(outputdir, basename + '.out') + err_file_name = os.path.join(outputdir, basename + '.err') + out_file = open(out_file_name, 'w', encoding=encoding) + err_file = open(err_file_name, 'w', encoding=encoding) + # Note that command is a string, which must be converted to list + # Must double-escape any backslashes so that they survive `shlex.split()` + script = os.path.join(outputdir, basename) + # `shlex.split()` only works with Unicode after 2.7.2 + if (sys.version_info.major == 2 and sys.version_info.micro < 3): + exec_cmd = shlex.split(bytes(command.format(file=script.replace('\\', '\\\\')))) + exec_cmd = [unicode(elem) for elem in exec_cmd] + else: + exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\'))) + # Add any created files due to the command + # This needs to be done before attempts to execute, to prevent orphans + for f in command_created: + files.append(f.format(file=script)) + try: + proc = subprocess.Popen(exec_cmd, stdout=out_file, stderr=err_file) + except WindowsError as e: + if e.errno == 2: + # Batch files won't be found when called without extension. They + # would be found if `shell=True`, but then getting the right + # exit code is tricky. So we perform some `cmd` trickery that + # is essentially equivalent to `shell=True`, but gives correct + # exit codes. Note that `subprocess.Popen()` works with strings + # under Windows; a list is not required. + exec_cmd_string = ' '.join(exec_cmd) + exec_cmd_string = 'cmd /C "@echo off & call {0} & if errorlevel 1 exit 1"'.format(exec_cmd_string) + proc = subprocess.Popen(exec_cmd_string, stdout=out_file, stderr=err_file) + else: + raise + + proc.wait() + out_file.close() + err_file.close() + + # Process saved stdout into file(s) that are included in the TeX document. + # + # Go through the saved output line by line, and save any printed content + # to its own file, named based on instance. + # + # The very end of the stdout lists dependencies, if any, so we start by + # removing and processing those. + if not os.path.isfile(out_file_name): + messages.append('* PythonTeX error') + messages.append(' Missing output file for ' + key_run.replace('#', ':')) + errors += 1 + else: + f = open(out_file_name, 'r', encoding=encoding) + out = f.read() + f.close() + try: + out, created = out.rsplit('=>PYTHONTEX:CREATED#\n', 1) + out, deps = out.rsplit('=>PYTHONTEX:DEPENDENCIES#\n', 1) + valid_stdout = True + except: + valid_stdout = False + if proc.returncode == 0: + raise ValueError('Missing "created" and/or "dependencies" delims in stdout; invalid template?') + + if valid_stdout: + # Add created files to created list + for c in created.splitlines(): + files.append(c) + + # Create a set of dependencies, to eliminate duplicates in the event + # that there are any. This is mainly useful when dependencies are + # automatically determined (for example, through redefining open()), + # may be specified multiple times as a result, and are hashed (and + # of a large enough size that hashing time is non-negligible). + deps = set([dep for dep in deps.splitlines()]) + # Process dependencies; get mtimes and (if specified) hashes + for dep in deps: + dep_file = os.path.expanduser(os.path.normcase(dep)) + if not os.path.isabs(dep_file): + dep_file = os.path.join(workingdir, dep_file) + if not os.path.isfile(dep_file): + # If we can't find the file, we return a null hash and issue + # an error. We don't need to change the exit status. If the + # code does depend on the file, there will be a separate + # error when the code attempts to use the file. If the code + # doesn't really depend on the file, then the error will be + # raised again anyway the next time PythonTeX runs when the + # dependency is listed but not found. + dependencies[dep] = (None, None) + messages.append('* PythonTeX error') + messages.append(' Cannot find dependency "' + dep + '"') + messages.append(' It belongs to ' + key_run.replace('#', ':')) + messages.append(' Relative paths to dependencies must be specified from the working directory.') + errors += 1 + elif hashdependencies: + # Read and hash the file in binary. Opening in text mode + # would require an unnecessary decoding and encoding cycle. + hasher = sha1() + f = open(dep_file, 'rb') + hasher.update(f.read()) + f.close() + dependencies[dep] = (os.path.getmtime(dep_file), hasher.hexdigest()) + else: + dependencies[dep] = (os.path.getmtime(dep_file), '') + + for block in out.split('=>PYTHONTEX:STDOUT#')[1:]: + if block: + delims, content = block.split('#\n', 1) + if content: + input_instance, input_command = delims.split('#') + if input_instance.endswith('CC'): + messages.append('* PythonTeX warning') + messages.append(' Custom code for "' + input_family + '" attempted to print or write to stdout') + messages.append(' This is not supported; use a normal code command or environment') + messages.append(' The following content was written:') + messages.append('') + messages.extend([' ' + l for l in content.splitlines()]) + warnings += 1 + elif input_command == 'i': + content = r'\pytx@SVMCR{pytx@MCR@' + key_run.replace('#', '@') + '@' + input_instance + '}\n' + content.rstrip('\n') + '\\endpytx@SVMCR\n\n' + macros.append(content) + else: + fname = os.path.join(outputdir, basename + '_' + input_instance + '.stdout') + f = open(fname, 'w', encoding=encoding) + f.write(content) + f.close() + files.append(fname) + + # Process stderr + if not os.path.isfile(err_file_name): + messages.append('* PythonTeX error') + messages.append(' Missing stderr file for ' + key_run.replace('#', ':')) + errors += 1 + else: + # Open error and code files. + f = open(err_file_name, encoding=encoding) + err = f.readlines() + f.close() + # Divide stderr into an undelimited and a delimited portion + found = False + for n, line in enumerate(err): + if line.startswith('=>PYTHONTEX:STDERR#'): + found = True + err_ud = err[:n] + err_d = err[n:] + break + if not found: + err_ud = err + err_d = [] + # Create a dict for storing any stderr content that will be saved + err_dict = defaultdict(list) + # Create the full basename that will be replaced in stderr + # We need two versions, one with the correct slashes for the OS, + # and one with the opposite slashes. This is needed when a language + # doesn't obey the OS's slash convention in paths given in stderr. + # For example, Windows uses backslashes, but Ruby under Windows uses + # forward in paths given in stderr. + fullbasename_correct = os.path.join(outputdir, basename) + if '\\' in fullbasename_correct: + fullbasename_reslashed = fullbasename_correct.replace('\\', '/') + else: + fullbasename_reslashed = fullbasename_correct.replace('/', '\\') + + if err_ud: + it = iter(code_index.items()) + index_now = next(it) + index_next = index_now + start_errgobble = None + for n, line in enumerate(err_ud): + if basename in line: + # Get the gobbleation. This is used to determine if + # other lines containing the basename are a continuation, + # or separate messages. + errgobble = match('(\s*)', line).groups()[0] + if start_errgobble is None: + start_errgobble = errgobble + # Only issue a message and track down the line numer if + # this is indeed the start of a new message, rather than + # a continuation of an old message that happens to + # contain the basename + if errgobble == start_errgobble: + # Determine the corresponding line number in the document + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + while index_next[1].lines_total < errlinenum: + try: + index_now, index_next = index_next, next(it) + except: + break + if errlinenum > index_now[1].lines_total + index_now[1].lines_input: + doclinenum = str(index_now[1].input_line_int + index_now[1].lines_input) + else: + doclinenum = str(index_now[1].input_line_int + errlinenum - index_now[1].lines_total - 1) + input_file = index_now[1].input_file + else: + doclinenum = '??' + input_file = '??' + + # Try to determine if we are dealing with an error or a + # warning. + found = False + index = n + if stderrlookbehind: + while index >= 0: + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + past_line = err_ud[index] + if (index < n and basename in past_line): + break + for pattern in warningsig: + if pattern in past_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in past_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index -= 1 + else: + while index < len(err_ud): + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + future_line = err_ud[index] + if (index > n and basename in future_line and + future_line.startswith(start_errgobble)): + break + for pattern in warningsig: + if pattern in future_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in future_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index += 1 + # If an error or warning wasn't positively identified, + # increment unknowns. + if not found: + unknowns += 1 + alert_type = 'unknown' + if input_file: + err_messages_ud.append('* PythonTeX stderr - {0} on line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + err_messages_ud.append('* PythonTeX stderr - {0} on line {1}:'.format(alert_type, doclinenum)) + err_messages_ud.append(' ' + line.replace(outputdir, '<outputdir>').rstrip('\n')) + else: + err_messages_ud.append(' ' + line.rstrip('\n')) + + # Create .stderr + if makestderr and err_messages_ud: + process = False + it = iter(code_index.items()) + index_now = next(it) + index_next = index_now + it_last = it + index_now_last = index_now + index_next_last = index_next + err_key_last_int = -1 + for n, line in enumerate(err_ud): + if basename in line: + # Determine the corresponding line number in the document + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + if index_next[1].lines_total >= errlinenum: + it = it_last + index_now = index_now_last + index_next = index_next_last + else: + it_last = it + index_now_last = index_now + index_next_last = index_next + while index_next[1].lines_total < errlinenum: + try: + index_now, index_next = index_next, next(it) + except: + index_now = index_next + break + if index_now[0].endswith('CC'): + process = False + else: + process = True + if len(index_now[1].input_command) > 1: + if errlinenum > index_now[1].lines_total + index_now[1].lines_input: + codelinenum = str(index_now[1].lines_user + index_now[1].lines_input + 1) + else: + codelinenum = str(index_now[1].lines_user + errlinenum - index_now[1].lines_total - index_now[1].inline_count) + else: + codelinenum = '1' + else: + codelinenum = '??' + messages.append('* PythonTeX error') + messages.append(' Line number ' + str(errlinenum) + ' could not be synced with the document') + messages.append(' Content from stderr is not delimited, and cannot be resolved') + errors += 1 + process = False + + if process: + if int(index_now[0]) > err_key_last_int: + err_key = basename + '_' + index_now[0] + err_key_last_int = int(index_now[0]) + line = line.replace(str(errlinenum), str(codelinenum), 1) + if fullbasename_correct in line: + fullbasename = fullbasename_correct + else: + fullbasename = fullbasename_reslashed + if stderrfilename == 'full': + line = line.replace(fullbasename, basename) + elif stderrfilename == 'session': + line = line.replace(fullbasename, input_session) + elif stderrfilename == 'genericfile': + line = line.replace(fullbasename + '.' + extension, '<file>') + elif stderrfilename == 'genericscript': + line = line.replace(fullbasename + '.' + extension, '<script>') + err_dict[err_key].append(line) + elif process: + err_dict[err_key].append(line) + + if err_d: + start_errgobble = None + msg = [] + found_basename = False + for n, line in enumerate(err_d): + if line.startswith('=>PYTHONTEX:STDERR#'): + # Store the last group of messages. Messages + # can't be directly appended to the main list, because + # a PythonTeX message must be inserted at the beginning + # of each chunk of stderr that never references + # the script that was executed. If the script is never + # referenced, then line numbers aren't automatically + # synced. These types of situations are created by + # warnings.warn() etc. + if msg: + if not found_basename: + # Get line number for command or beginning of + # environment + input_instance = last_delim.split('#')[1] + doclinenum = str(code_index[input_instance].input_line_int) + input_file = code_index[input_instance].input_file + # Try to identify alert. We have to parse all + # lines for signs of errors and warnings. This + # may result in overcounting, but it's the best + # we can do--otherwise, we could easily + # undercount, or, finding a warning, miss a + # subsequent error. When this code is actually + # used, it's already a sign that normal parsing + # has failed. + found_error = False + found_warning = False + for l in msg: + for pattern in warningsig: + if pattern in l: + warnings += 1 + found_warning = True + for pattern in errorsig: + if pattern in l: + errors += 1 + found_warning = True + if found_error: + alert_type = 'error' + elif found_warning: + alert_type = 'warning' + else: + unknowns += 1 + alert_type = 'unknown' + if input_file: + err_messages_d.append('* PythonTeX stderr - {0} near line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + err_messages_d.append('* PythonTeX stderr - {0} near line {1}:'.format(alert_type, doclinenum)) + err_messages_d.extend(msg) + msg = [] + found_basename = False + # Never process delimiting info until it is used + # Rather, store the index of the last delimiter + last_delim = line + elif basename in line: + found_basename = True + # Get the gobbleation. This is used to determine if + # other lines containing the basename are a continuation, + # or separate messages. + errgobble = match('(\s*)', line).groups()[0] + if start_errgobble is None: + start_errgobble = errgobble + # Only issue a message and track down the line numer if + # this is indeed the start of a new message, rather than + # a continuation of an old message that happens to + # contain the basename + if errgobble == start_errgobble: + # Determine the corresponding line number in the document + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + # Get info from last delim + input_instance, input_command = last_delim.split('#')[1:-1] + # Calculate the line number in the document + ei = code_index[input_instance] + if errlinenum > ei.lines_total + ei.lines_input: + doclinenum = str(ei.input_line_int + ei.lines_input) + else: + doclinenum = str(ei.input_line_int + errlinenum - ei.lines_total - 1) + input_file = ei.input_file + else: + doclinenum = '??' + input_file = '??' + + # Try to determine if we are dealing with an error or a + # warning. + found = False + index = n + if stderrlookbehind: + while index >= 0: + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + past_line = err_d[index] + if (past_line.startswith('=>PYTHONTEX:STDERR#') or + (index < n and basename in past_line)): + break + for pattern in warningsig: + if pattern in past_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in past_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index -= 1 + else: + while index < len(err_d): + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + future_line = err_d[index] + if (future_line.startswith('=>PYTHONTEX:STDERR#') or + (index > n and basename in future_line and future_line.startswith(start_errgobble))): + break + for pattern in warningsig: + if pattern in future_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in future_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index += 1 + # If an error or warning wasn't positively identified, + # assume error for safety but indicate uncertainty. + if not found: + unknowns += 1 + alert_type = 'unknown' + if input_file: + msg.append('* PythonTeX stderr - {0} on line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + msg.append('* PythonTeX stderr - {0} on line {1}:'.format(alert_type, doclinenum)) + # Clean up the stderr format a little, to keep it compact + line = line.replace(outputdir, '<outputdir>').rstrip('\n') + if '/<outputdir>' in line or '\\<outputdir>' in line: + line = sub(r'(?:(?:[A-Z]:\\)|(?:~?/)).*<outputdir>', '<outputdir>', line) + msg.append(' ' + line) + else: + msg.append(' ' + line.rstrip('\n')) + # Deal with any leftover messages + if msg: + if not found_basename: + # Get line number for command or beginning of + # environment + input_instance = last_delim.split('#')[1] + doclinenum = str(code_index[input_instance].input_line_int) + input_file = code_index[input_instance].input_file + # Try to identify alert. We have to parse all + # lines for signs of errors and warnings. This + # may result in overcounting, but it's the best + # we can do--otherwise, we could easily + # undercount, or, finding a warning, miss a + # subsequent error. When this code is actually + # used, it's already a sign that normal parsing + # has failed. + found_error = False + found_warning = False + for l in msg: + for pattern in warningsig: + if pattern in l: + warnings += 1 + found_warning = True + for pattern in errorsig: + if pattern in l: + errors += 1 + found_warning = True + if found_error: + alert_type = 'error' + elif found_warning: + alert_type = 'warning' + else: + unknowns += 1 + alert_type = 'unknown' + if input_file: + err_messages_d.append('* PythonTeX stderr - {0} near line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + err_messages_d.append('* PythonTeX stderr - {0} near line {1}:'.format(alert_type, doclinenum)) + err_messages_d.extend(msg) + + # Create .stderr + if makestderr and err_messages_d: + process = False + for n, line in enumerate(err_d): + if line.startswith('=>PYTHONTEX:STDERR#'): + input_instance, input_command = line.split('#')[1:-1] + if input_instance.endswith('CC'): + process = False + else: + process = True + err_key = basename + '_' + input_instance + elif process and basename in line: + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + # Calculate the line number in the document + # Account for inline + ei = code_index[input_instance] + # Store the `input_instance` in case it's + # incremented later + last_input_instance = input_instance + # If the error or warning was actually triggered + # later on (for example, multiline string with + # missing final delimiter), look ahead and + # determine the correct input_instance, so that + # we get the correct line number. We don't + # associate the created stderr with this later + # instance, however, but rather with the instance + # in which the error began. Doing that might + # possibly be preferable in some cases, but would + # also require that the current stderr be split + # between multiple instances, requiring extra + # parsing. + while errlinenum > ei.lines_total + ei.lines_input: + next_input_instance = str(int(input_instance) + 1) + if next_input_instance in code_index: + next_ei = code_index[next_input_instance] + if errlinenum > next_ei.lines_total: + input_instance = next_input_instance + ei = next_ei + else: + break + else: + break + if len(input_command) > 1: + if errlinenum > ei.lines_total + ei.lines_input: + codelinenum = str(ei.lines_user + ei.lines_input + 1) + else: + codelinenum = str(ei.lines_user + errlinenum - ei.lines_total - ei.inline_count) + else: + codelinenum = '1' + # Reset `input_instance`, in case incremented + input_instance = last_input_instance + else: + codelinenum = '??' + messages.append('* PythonTeX notice') + messages.append(' Line number ' + str(errlinenum) + ' could not be synced with the document') + + line = line.replace(str(errlinenum), str(codelinenum), 1) + if fullbasename_correct in line: + fullbasename = fullbasename_correct + else: + fullbasename = fullbasename_reslashed + if stderrfilename == 'full': + line = line.replace(fullbasename, basename) + elif stderrfilename == 'session': + line = line.replace(fullbasename, input_session) + elif stderrfilename == 'genericfile': + line = line.replace(fullbasename + '.' + extension, '<file>') + elif stderrfilename == 'genericscript': + line = line.replace(fullbasename + '.' + extension, '<script>') + err_dict[err_key].append(line) + elif process: + err_dict[err_key].append(line) + if err_dict: + for err_key in err_dict: + stderr_file_name = os.path.join(outputdir, err_key + '.stderr') + f = open(stderr_file_name, 'w', encoding=encoding) + f.write(''.join(err_dict[err_key])) + f.close() + files.append(stderr_file_name) + + # Clean up temp files, and update the list of existing files + if keeptemps == 'none': + for ext in [extension, 'pytxmcr', 'out', 'err']: + fname = os.path.join(outputdir, basename + '.' + ext) + if os.path.isfile(fname): + os.remove(fname) + elif keeptemps == 'code': + for ext in ['pytxmcr', 'out', 'err']: + fname = os.path.join(outputdir, basename + '.' + ext) + if os.path.isfile(fname): + os.remove(fname) + files.append(os.path.join(outputdir, basename + '.' + extension)) + elif keeptemps == 'all': + for ext in [extension, 'pytxmcr', 'out', 'err']: + files.append(os.path.join(outputdir, basename + '.' + ext)) + + # Take care of any unknowns, based on exit code + # Interpret the exit code as an indicator of whether there were errors, + # and treat unknowns accordingly. This will cause all warnings to be + # misinterpreted as errors if warnings trigger a nonzero exit code. + # It will also cause all warnings to be misinterpreted as errors if there + # is a single error that causes a nonzero exit code. That isn't ideal, + # but shouldn't be a problem, because as soon as the error(s) are fixed, + # the exit code will be zero, and then all unknowns will be interpreted + # as warnings. + if unknowns: + if proc.returncode == 0: + unknowns_type = 'warnings' + warnings += unknowns + else: + unknowns_type = 'errors' + errors += unknowns + unknowns_message = ''' + * PythonTeX notice + {0} message(s) could not be classified + Based on the return code, they were interpreted as {1}''' + messages[0] += textwrap.dedent(unknowns_message.format(unknowns, unknowns_type)) + + # Take care of anything that has escaped detection thus far. + if proc.returncode == 1 and not errors: + errors += 1 + command_message = ''' + * PythonTeX error + An error occurred but no error messages were identified. + This may indicate a bad command or missing program. + The following command was executed: + "{0}"''' + messages[0] += textwrap.dedent(command_message.format(' '.join(exec_cmd))) + + # Add any stderr messages; otherwise, clear the default message header + if err_messages_ud: + messages.extend(err_messages_ud) + if err_messages_d: + messages.extend(err_messages_d) + if len(messages) == 1: + messages = [] + + # Return a dict of dicts of results + return {'process': 'code', + 'key': key_run, + 'files': files, + 'macros': macros, + 'dependencies': dependencies, + 'errors': errors, + 'warnings': warnings, + 'messages': messages} + + + + +def do_pygments(encoding, outputdir, fvextfile, pygments_list, + pygments_settings, typeset_cache): + ''' + Create Pygments content. + + To be run during multiprocessing. + ''' + # Lazy import + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import LatexFormatter + + # Create what's needed for storing results + pygments_files = defaultdict(list) + pygments_macros = defaultdict(list) + errors = 0 + warnings = 0 + messages = [] + messages.append('\n---- Messages for Pygments ----') + + # Create dicts of formatters and lexers. + formatter = dict() + lexer = dict() + for codetype in pygments_settings: + if codetype != ':GLOBAL': + formatter[codetype] = LatexFormatter(**pygments_settings[codetype]['formatter_options']) + lexer[codetype] = get_lexer_by_name(pygments_settings[codetype]['lexer'], + **pygments_settings[codetype]['lexer_options']) + + # Actually parse and highlight the code. + for c in pygments_list: + if c.is_cons: + content = typeset_cache[c.key_run][c.input_instance] + elif c.is_extfile: + if os.path.isfile(c.extfile): + f = open(c.extfile, encoding=encoding) + content = f.read() + f.close() + else: + content = None + messages.append('* PythonTeX error') + messages.append(' Could not find external file ' + c.extfile) + messages.append(' The file was not pygmentized') + else: + content = c.code + processed = highlight(content, lexer[c.input_family], formatter[c.input_family]) + if c.is_inline or content.count('\n') < fvextfile: + # Highlighted code brought in via macros needs SaveVerbatim + processed = sub(r'\\begin{Verbatim}\[(.+)\]', + r'\\begin{{SaveVerbatim}}[\1]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.input_family, c.input_session, c.input_restart, c.input_instance), processed, count=1) + processed = processed.rsplit('\\', 1)[0] + '\\end{SaveVerbatim}\n\n' + pygments_macros[c.key_typeset].append(processed) + else: + fname = os.path.join(outputdir, c.key_typeset.replace('#', '_') + '.pygtex') + f = open(fname, 'w', encoding=encoding) + f.write(processed) + f.close() + pygments_files[c.key_typeset].append(fname) + + if len(messages) == 1: + messages = [] + # Return a dict of dicts of results + return {'process': 'pygments', + 'pygments_files': pygments_files, + 'pygments_macros': pygments_macros, + 'errors': errors, + 'warnings': warnings, + 'messages': messages} + + + + +def python_console(jobname, encoding, outputdir, workingdir, fvextfile, + pygments_settings, cc_begin_list, cons_list, cc_end_list, + startup, banner, filename): + ''' + Use Python's ``code`` module to typeset emulated Python interactive + sessions, optionally highlighting with Pygments. + ''' + # Create what's needed for storing results + key_run = cons_list[0].key_run + files = [] + macros = [] + pygments_files = defaultdict(list) + pygments_macros = defaultdict(list) + typeset_cache = {} + dependencies = {} + errors = 0 + warnings = 0 + messages = [] + messages.append('\n---- Messages for ' + key_run.replace('#', ':') + ' ----') + + # Lazy import what's needed + import code + from collections import deque + if sys.version_info[0] == 2: + # Need a Python 2 interface to io.StringIO that can accept bytes + import io + class StringIO(io.StringIO): + _orig_write = io.StringIO.write + def write(self, s): + self._orig_write(unicode(s)) + else: + from io import StringIO + + # Create a custom console class + class Console(code.InteractiveConsole): + ''' + A subclass of code.InteractiveConsole that takes a list and treats it + as a series of console input. + ''' + + def __init__(self, banner, filename): + if banner == 'none': + self.banner = 'NULL BANNER' + elif banner == 'standard': + cprt = 'Type "help", "copyright", "credits" or "license" for more information.' + self.banner = 'Python {0} on {1}\n{2}'.format(sys.version, sys.platform, cprt) + elif banner == 'pyversion': + self.banner = 'Python ' + '.'.join(str(sys.version_info[n]) for n in (0, 1, 2)) + else: + self.banner = None + if filename == 'console': + self.filename = '<console>' + elif filename == 'stdin': + self.filename = '<stdin>' + else: + self.filename = None + code.InteractiveConsole.__init__(self, filename=self.filename) + self.iostdout = StringIO() + + def consolize(self, startup, cons_list): + self.console_code = deque() + # Delimiters are passed straight through and need newlines + self.console_code.append('=>PYTHONTEX#STARTUP##\n') + cons_config = ''' + import os + import sys + docdir = os.getcwd() + if os.path.isdir('{workingdir}'): + os.chdir('{workingdir}') + if os.getcwd() not in sys.path: + sys.path.append(os.getcwd()) + else: + sys.exit('Cannot find directory {workingdir}') + + if docdir not in sys.path: + sys.path.append(docdir) + + del docdir + ''' + cons_config = cons_config.format(workingdir=workingdir)[1:] + self.console_code.extend(textwrap.dedent(cons_config).splitlines()) + # Code is processed and doesn't need newlines + self.console_code.extend(startup.splitlines()) + for c in cons_list: + self.console_code.append('=>PYTHONTEX#{0}#{1}#\n'.format(c.input_instance, c.input_command)) + self.console_code.extend(c.code.splitlines()) + old_stdout = sys.stdout + sys.stdout = self.iostdout + self.interact(self.banner) + sys.stdout = old_stdout + self.session_log = self.iostdout.getvalue() + + def raw_input(self, prompt): + # Have to do a lot of looping and trying to make sure we get + # something valid to execute + try: + line = self.console_code.popleft() + except IndexError: + raise EOFError + while line.startswith('=>PYTHONTEX#'): + # Get new lines until we get one that doesn't begin with a + # delimiter. Then write the last delimited line. + old_line = line + try: + line = self.console_code.popleft() + self.write(old_line) + except IndexError: + raise EOFError + if line or prompt == sys.ps2: + self.write('{0}{1}\n'.format(prompt, line)) + else: + self.write('\n') + return line + + def write(self, data): + self.iostdout.write(data) + + # Need to combine all custom code and user code to pass to consolize + cons_list = cc_begin_list + cons_list + cc_end_list + # Create a dict for looking up exceptions. This is needed for startup + # commands and for code commands and environments, since their output + # isn't typeset + cons_index = {} + for c in cons_list: + cons_index[c.input_instance] = c.input_line + + # Consolize the code + con = Console(banner, filename) + con.consolize(startup, cons_list) + + # Set up Pygments, if applicable + if pygments_settings is not None: + pygmentize = True + # Lazy import + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import LatexFormatter + formatter = LatexFormatter(**pygments_settings['formatter_options']) + lexer = get_lexer_by_name(pygments_settings['lexer'], + **pygments_settings['lexer_options']) + else: + pygmentize = False + + # Process the console output + output = con.session_log.split('=>PYTHONTEX#') + # Extract banner + if banner == 'none': + banner_text = '' + else: + banner_text = output[0] + # Ignore the beginning, because it's the banner + for block in output[1:]: + delims, console_content = block.split('#\n', 1) + if console_content: + input_instance, input_command = delims.split('#') + if input_instance == 'STARTUP': + exception = False + console_content_lines = console_content.splitlines() + for line in console_content_lines: + if (not line.startswith(sys.ps1) and + not line.startswith(sys.ps2) and + line and not line.isspace()): + exception = True + break + if exception: + if 'Error:' in console_content: + errors += 1 + alert_type = 'error' + elif 'Warning:' in console_content: + warnings += 1 + alert_type = 'warning' + else: + errors += 1 + alert_type = 'error (?)' + messages.append('* PythonTeX stderr - {0} in console startup code:'.format(alert_type)) + for line in console_content_lines: + messages.append(' ' + line) + elif input_command in ('c', 'code'): + exception = False + console_content_lines = console_content.splitlines() + for line in console_content_lines: + if (line and not line.startswith(sys.ps1) and + not line.startswith(sys.ps2) and + not line.isspace()): + print('X' + line + 'X') + exception = True + break + if exception: + if 'Error:' in console_content: + errors += 1 + alert_type = 'error' + elif 'Warning:' in console_content: + warnings += 1 + alert_type = 'warning' + else: + errors += 1 + alert_type = 'error (?)' + if input_instance.endswith('CC'): + messages.append('* PythonTeX stderr - {0} near line {1} in custom code for console:'.format(alert_type, cons_index[input_instance])) + else: + messages.append('* PythonTeX stderr - {0} near line {1} in console code:'.format(alert_type, cons_index[input_instance])) + messages.append(' Console code is not typeset, and should have no output') + for line in console_content_lines: + messages.append(' ' + line) + else: + if input_command == 'i': + # Currently, there isn't any error checking for invalid + # content; it is assumed that a single line of commands + # was entered, producing one or more lines of output. + # Given that the current ``\pycon`` command doesn't + # allow line breaks to be written to the .pytxcode, that + # should be a reasonable assumption. + console_content = console_content.split('\n', 1)[1] + if banner_text is not None and input_command == 'console': + # Append banner to first appropriate environment + console_content = banner_text + console_content + banner_text = None + # Cache + key_typeset = key_run + '#' + input_instance + typeset_cache[input_instance] = console_content + # Process for LaTeX + if pygmentize: + processed = highlight(console_content, lexer, formatter) + if console_content.count('\n') < fvextfile: + processed = sub(r'\\begin{Verbatim}\[(.+)\]', + r'\\begin{{SaveVerbatim}}[\1]{{pytx@{0}}}'.format(key_typeset.replace('#', '@')), + processed, count=1) + processed = processed.rsplit('\\', 1)[0] + '\\end{SaveVerbatim}\n\n' + pygments_macros[key_typeset].append(processed) + else: + fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.pygtex') + f = open(fname, 'w', encoding=encoding) + f.write(processed) + f.close() + pygments_files[key_typeset].append(fname) + else: + if console_content.count('\n') < fvextfile: + processed = ('\\begin{{SaveVerbatim}}{{pytx@{0}}}\n'.format(key_typeset.replace('#', '@')) + + console_content + '\\end{SaveVerbatim}\n\n') + macros.append(processed) + else: + processed = ('\\begin{Verbatim}\n' + console_content + + '\\end{Verbatim}\n\n') + fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.tex') + f = open(fname, 'w', encoding=encoding) + f.write(processed) + f.close() + files.append(fname) + + if len(messages) == 1: + messages = [] + + # Return a dict of dicts of results + return {'process': 'console', + 'key': key_run, + 'files': files, + 'macros': macros, + 'pygments_files': pygments_files, + 'pygments_macros': pygments_macros, + 'typeset_cache': typeset_cache, + 'dependencies': dependencies, + 'errors': errors, + 'warnings': warnings, + 'messages': messages} + + + + +def main(): + # Create dictionaries for storing data. + # + # All data that must be saved for subsequent runs is stored in "data". + # (We start off by saving the script version, a global var, in this dict.) + # All data that is only created for this run is stored in "temp_data". + # (We start off by creating keys for keeping track of errors and warnings.) + # All old data will eventually be loaded into "old_data". + # Since dicts are mutable data types, these dicts can be modified + # from within functions, as long as the dicts are passed to the functions. + # For simplicity, variables will often be created within functions to + # refer to dictionary values. + data = {'version': version, 'start_time': time.time()} + temp_data = {'errors': 0, 'warnings': 0} + old_data = dict() + + + # Process command-line options. + # + # This gets the raw_jobname (actual job name), jobname (a sanitized job + # name, used for creating files named after the jobname), and any options. + process_argv(data, temp_data) + # If there aren't errors in argv, and the program is going to run + # (rather than just exit due to --version or --help command-line options), + # print PythonTeX version. Flush to make the message go out immediately, + # so that the user knows PythonTeX has started. + print('This is PythonTeX ' + version) + sys.stdout.flush() + # Once we have the encoding (from argv), we set stdout and stderr to use + # this encoding. Later, we will parse the saved stderr of scripts + # executed via multiprocessing subprocesses, and print the parsed results + # to stdout. The saved stderr uses the same encoding that was used + # for the files that created it (this is important for code containing + # unicode characters), so we also need stdout for the main PythonTeX + # script to support this encoding. Setting stderr encoding is primarily + # a matter of symmetry. Ideally, pythontex*.py will be bug-free, + # and stderr won't be needed! + if sys.version_info[0] == 2: + sys.stdout = codecs.getwriter(data['encoding'])(sys.stdout, 'strict') + sys.stderr = codecs.getwriter(data['encoding'])(sys.stderr, 'strict') + else: + sys.stdout = codecs.getwriter(data['encoding'])(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter(data['encoding'])(sys.stderr.buffer, 'strict') + + + # Load the code and process the settings it passes from the TeX side. + # + # This gets a list containing the code (the part of the code file + # containing the settings is removed) and the processed settings. + load_code_get_settings(data, temp_data) + # Now that the settings are loaded, check if outputdir exits. + # If not, create it. + if not os.path.isdir(data['settings']['outputdir']): + os.mkdir(data['settings']['outputdir']) + + + # Load/create old_data + get_old_data(data, old_data, temp_data) + + + # Hash the code. Determine what needs to be executed. Determine whether + # Pygments should be used. Update pygments_settings to account for + # Pygments commands and environments (as opposed to PythonTeX commands + # and environments). + hash_all(data, temp_data, old_data, engine_dict) + + + # Parse the code and write scripts for execution. + parse_code_write_scripts(data, temp_data, engine_dict) + + + # Execute the code and perform Pygments highlighting via multiprocessing. + do_multiprocessing(data, temp_data, old_data, engine_dict) + + + # Print exit message + print('\n--------------------------------------------------') + # If some rerun settings are used, there may be unresolved errors or + # warnings; if so, print a summary of those along with the current + # error and warning summary + unresolved_errors = 0 + unresolved_warnings = 0 + if temp_data['rerun'] in ('errors', 'modified', 'never'): + global_update = {} + global_update.update(temp_data['code_update']) + global_update.update(temp_data['cons_update']) + for key in data['exit_status']: + if not global_update[key]: + unresolved_errors += data['exit_status'][key][0] + unresolved_warnings += data['exit_status'][key][1] + if unresolved_warnings != 0 or unresolved_errors != 0: + print('PythonTeX: {0}'.format(data['raw_jobname'])) + print(' - Old: {0} error(s), {1} warnings(s)'.format(unresolved_errors, unresolved_warnings)) + print(' - Current: {0} error(s), {1} warnings(s)'.format(temp_data['errors'], temp_data['warnings'])) + else: + print('PythonTeX: {0} - {1} error(s), {2} warning(s)\n'.format(data['raw_jobname'], temp_data['errors'], temp_data['warnings'])) + + # Exit with appropriate exit code based on user settings. + if temp_data['error_exit_code'] and temp_data['errors'] > 0: + sys.exit(1) + else: + sys.exit() + + + +# The "if" statement is needed for multiprocessing under Windows; see the +# multiprocessing documentation. It is also needed in this case when the +# script is invoked via the wrapper. +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/Master/texmf-dist/scripts/pythontex/pythontex3.py b/Master/texmf-dist/scripts/pythontex/pythontex3.py new file mode 100755 index 00000000000..1712e75e096 --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex3.py @@ -0,0 +1,2360 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +This is the main PythonTeX script. It should be launched via pythontex.py. + +Two versions of this script are provided. One, with name ending in "2", runs +under Python 2.7. The other, with name ending in "3", runs under Python 3.2+. + +This script needs to be able to import pythontex_engines.py; in general it +should be in the same directory. + + +Licensed under the BSD 3-Clause License: + +Copyright (c) 2012-2013, Geoffrey M. Poore + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the <organization> nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +''' + + +# Imports +#// Python 2 +#from __future__ import absolute_import +#from __future__ import division +#from __future__ import print_function +#from __future__ import unicode_literals +#\\ End Python 2 +import sys +import os +import argparse +import codecs +import time +from hashlib import sha1 +from collections import defaultdict, OrderedDict, namedtuple +from re import match, sub, search +import subprocess +import multiprocessing +from pygments.styles import get_all_styles +from pythontex_engines import * +import textwrap + +if sys.version_info[0] == 2: + try: + import cPickle as pickle + except: + import pickle + from io import open +else: + import pickle + + + + +# Script parameters +# Version +version = 'v0.12' + + + + +class Pytxcode(object): + def __init__(self, data, gobble): + self.delims, self.code = data.split('#\n', 1) + self.input_family, self.input_session, self.input_restart, self.input_instance, self.input_command, self.input_context, self.input_args_run, self.input_args_prettyprint, self.input_file, self.input_line = self.delims.split('#') + self.input_instance_int = int(self.input_instance) + self.input_line_int = int(self.input_line) + self.key_run = self.input_family + '#' + self.input_session + '#' + self.input_restart + self.key_typeset = self.key_run + '#' + self.input_instance + self.hashable_delims_run = self.key_typeset + '#' + self.input_command + '#' + self.input_context + '#' + self.input_args_run + self.hashable_delims_typeset = self.key_typeset + '#' + self.input_command + '#' + self.input_context + '#' + self.input_args_run + if len(self.input_command) > 1: + self.is_inline = False + # Environments start on the next line + self.input_line_int += 1 + self.input_line = str(self.input_line_int) + else: + self.is_inline = True + self.is_extfile = True if self.input_session.startswith('EXT:') else False + if self.is_extfile: + self.extfile = os.path.expanduser(os.path.normcase(self.input_session.replace('EXT:', '', 1))) + self.is_cc = True if self.input_family.startswith('CC:') else False + self.is_pyg = True if self.input_family.startswith('PYG') else False + self.is_verb = True if self.input_restart.endswith('verb') else False + if self.is_cc: + self.input_instance += 'CC' + self.cc_type, self.cc_pos = self.input_family.split(':')[1:] + if self.is_verb or self.is_pyg or self.is_cc: + self.is_cons = False + else: + self.is_cons = engine_dict[self.input_family].console + self.is_code = False if self.is_verb or self.is_pyg or self.is_cc or self.is_cons else True + if self.input_command in ('c', 'code') or (self.input_command == 'i' and not self.is_cons): + self.is_typeset = False + else: + self.is_typeset = True + + if gobble == 'auto': + self.code = textwrap.dedent(self.code) + + + + +def process_argv(data, temp_data): + ''' + Process command line options using the argparse module. + + Most options are passed via the file of code, rather than via the command + line. + ''' + + # Create a command line argument parser + parser = argparse.ArgumentParser() + parser.add_argument('TEXNAME', + help='LaTeX file, with or without .tex extension') + parser.add_argument('--version', action='version', + version='PythonTeX {0}'.format(data['version'])) + parser.add_argument('--encoding', default='UTF-8', + help='encoding for all text files (see codecs module for encodings)') + parser.add_argument('--error-exit-code', default='true', + choices=('true', 'false'), + help='return exit code of 1 if there are errors (not desirable with some TeX editors and workflows)') + group = parser.add_mutually_exclusive_group() + group.add_argument('--runall', nargs='?', default='false', + const='true', choices=('true', 'false'), + help='run ALL code; equivalent to package option') + group.add_argument('--rerun', default='errors', + choices=('never', 'modified', 'errors', 'warnings', 'always'), + help='set conditions for rerunning code; equivalent to package option') + parser.add_argument('--hashdependencies', nargs='?', default='false', + const='true', choices=('true', 'false'), + help='hash dependencies (such as external data) to check for modification, rather than using mtime; equivalent to package option') + parser.add_argument('-v', '--verbose', default=False, action='store_true', + help='verbose output') + parser.add_argument('--interpreter', default=None, help='set a custom interpreter; argument should be in the form "<interpreter>:<command>, <interp>:<cmd>, ..." where <interpreter> is "python", "ruby", etc., and <command> is the command for invoking the interpreter; argument may also be in the form of a Python dictionary') + args = parser.parse_args() + + # Store the parsed argv in data and temp_data + data['encoding'] = args.encoding + if args.error_exit_code == 'true': + temp_data['error_exit_code'] = True + else: + temp_data['error_exit_code'] = False + # runall can be mapped onto rerun, so both are stored under rerun + if args.runall == 'true': + temp_data['rerun'] = 'always' + else: + temp_data['rerun'] = args.rerun + # hashdependencies need only be in temp_data, since changing it would + # change hashes (hashes of mtime vs. file contents) + if args.hashdependencies == 'true': + temp_data['hashdependencies'] = True + else: + temp_data['hashdependencies'] = False + temp_data['verbose'] = args.verbose + # Update interpreter_dict based on interpreter + if args.interpreter is not None: + interp_list = args.interpreter.lstrip('{').rstrip('}').split(',') + for interp in interp_list: + if interp: + try: + k, v = interp.split(':') + k = k.strip(' \'"') + v = v.strip(' \'"') + interpreter_dict[k] = v + except: + print('Invalid --interpreter argument') + return sys.exit(2) + + + if args.TEXNAME is not None: + # Determine if we a dealing with just a filename, or a name plus + # path. If there's a path, we need to make the document directory + # the current working directory. + dir, raw_jobname = os.path.split(args.TEXNAME) + dir = os.path.expanduser(os.path.normcase(dir)) + if dir: + os.chdir(dir) + sys.path.append(dir) + # If necessary, strip off an extension to find the raw jobname that + # corresponds to the .pytxcode. + if not os.path.exists(raw_jobname + '.pytxcode'): + raw_jobname = raw_jobname.rsplit('.', 1)[0] + if not os.path.exists(raw_jobname + '.pytxcode'): + print('* PythonTeX error') + print(' Code file ' + raw_jobname + '.pytxcode does not exist.') + print(' Run LaTeX to create it.') + return sys.exit(1) + + # We need a "sanitized" version of the jobname, with spaces and + # asterisks replaced with hyphens. This is done to avoid TeX issues + # with spaces in file names, paralleling the approach taken in + # pythontex.sty. From now on, we will use the sanitized version every + # time we create a file that contains the jobname string. The raw + # version will only be used in reference to pre-existing files created + # on the TeX side, such as the .pytxcode file. + jobname = raw_jobname.replace(' ', '-').replace('"', '').replace('*', '-') + # Store the results in data + data['raw_jobname'] = raw_jobname + data['jobname'] = jobname + + # We need to check to make sure that the "sanitized" jobname doesn't + # lead to a collision with a file that already has that name, so that + # two files attempt to use the same PythonTeX folder. + # + # If <jobname>.<ext> and <raw_jobname>.<ext> both exist, where <ext> + # is a common LaTeX extension, we exit. We operate under the + # assumption that there should be only a single file <jobname> in the + # document root directory that has a common LaTeX extension. That + # could be false, but if so, the user probably has worse things to + # worry about than a potential PythonTeX output collision. + # If <jobname>* and <raw_jobname>* both exist, we issue a warning but + # attempt to proceed. + if jobname != raw_jobname: + resolved = False + for ext in ('.tex', '.ltx', '.dtx'): + if os.path.isfile(raw_jobname + ext): + if os.path.isfile(jobname + ext): + print('* PythonTeX error') + print(' Directory naming collision between the following files:') + print(' ' + raw_jobname + ext) + print(' ' + jobname + ext) + return sys.exit(1) + else: + resolved = True + break + if not resolved: + ls = os.listdir('.') + for file in ls: + if file.startswith(jobname): + print('* PythonTeX warning') + print(' Potential directory naming collision between the following names:') + print(' ' + raw_jobname) + print(' ' + jobname + '*') + print(' Attempting to proceed.') + temp_data['warnings'] += 1 + break + + + + +def load_code_get_settings(data, temp_data): + ''' + Load the code file, preprocess the code, and extract the settings. + ''' + # Bring in the .pytxcode file as a single string + raw_jobname = data['raw_jobname'] + encoding = data['encoding'] + # The error checking here is a little redundant + if os.path.isfile(raw_jobname + '.pytxcode'): + f = open(raw_jobname + '.pytxcode', 'r', encoding=encoding) + pytxcode = f.read() + f.close() + else: + print('* PythonTeX error') + print(' Code file ' + raw_jobname + '.pytxcode does not exist.') + print(' Run LaTeX to create it.') + return sys.exit(1) + + # Split code and settings + try: + pytxcode, pytxsettings = pytxcode.rsplit('=>PYTHONTEX:SETTINGS#', 1) + except: + print('The .pytxcode file appears to have an outdated format or be invalid') + print('Run LaTeX to make sure the file is current') + return sys.exit(1) + + + # Prepare to process settings + # + # Create a dict for storing settings. + settings = {} + # Create a dict for storing Pygments settings. + # Each dict entry will itself be a dict. + pygments_settings = defaultdict(dict) + + # Create a dict of processing functions, and generic processing functions + settings_func = dict() + def set_kv_data(k, v): + if v == 'true': + settings[k] = True + elif v == 'false': + settings[k] = False + else: + settings[k] = v + # Need a function for when assignment is only needed if not default value + def set_kv_temp_data_if_not_default(k, v): + if v != 'default': + if v == 'true': + temp_data[k] = True + elif v == 'false': + temp_data[k] = False + else: + temp_data[k] = v + def set_kv_data_fvextfile(k, v): + # Error checking on TeX side should be enough, but be careful anyway + try: + v = int(v) + except ValueError: + print('* PythonTeX error') + print(' Unable to parse package option fvextfile.') + return sys.exit(1) + if v < 0: + settings[k] = sys.maxsize + elif v == 0: + settings[k] = 1 + print('* PythonTeX warning') + print(' Invalid value for package option fvextfile.') + temp_data['warnings'] += 1 + else: + settings[k] = v + def set_kv_pygments(k, v): + input_family, lexer_opts, options = v.replace(' ','').split('|') + lexer = None + lex_dict = {} + opt_dict = {} + if lexer_opts: + for l in lexer_opts.split(','): + if '=' in l: + k, v = l.split('=', 1) + if k == 'lexer': + lexer = l + else: + lex_dict[k] = v + else: + lexer = l + if options: + for o in options.split(','): + if '=' in o: + k, v = o.split('=', 1) + if v in ('true', 'True'): + v = True + elif v in ('false', 'False'): + v = False + else: + k = option + v = True + opt_dict[k] = v + if input_family != ':GLOBAL': + if 'lexer' in pygments_settings[':GLOBAL']: + lexer = pygments_settings[':GLOBAL']['lexer'] + lex_dict.update(pygments_settings[':GLOBAL']['lexer_options']) + opt_dict.update(pygments_settings[':GLOBAL']['formatter_options']) + if 'style' not in opt_dict: + opt_dict['style'] = 'default' + opt_dict['commandprefix'] = 'PYG' + opt_dict['style'] + if lexer is not None: + pygments_settings[input_family]['lexer'] = lexer + pygments_settings[input_family]['lexer_options'] = lex_dict + pygments_settings[input_family]['formatter_options'] = opt_dict + settings_func['version'] = set_kv_data + settings_func['outputdir'] = set_kv_data + settings_func['workingdir'] = set_kv_data + settings_func['gobble'] = set_kv_data + settings_func['rerun'] = set_kv_temp_data_if_not_default + settings_func['hashdependencies'] = set_kv_temp_data_if_not_default + settings_func['makestderr'] = set_kv_data + settings_func['stderrfilename'] = set_kv_data + settings_func['keeptemps'] = set_kv_data + settings_func['pyfuture'] = set_kv_data + settings_func['pyconfuture'] = set_kv_data + settings_func['pygments'] = set_kv_data + settings_func['fvextfile'] = set_kv_data_fvextfile + settings_func['pygglobal'] = set_kv_pygments + settings_func['pygfamily'] = set_kv_pygments + settings_func['pyconbanner'] = set_kv_data + settings_func['pyconfilename'] = set_kv_data + settings_func['depythontex'] = set_kv_data + + # Process settings + for line in pytxsettings.split('\n'): + if line: + key, val = line.split('=', 1) + try: + settings_func[key](key, val) + except KeyError: + print('* PythonTeX warning') + print(' Unknown option "' + key + '"') + temp_data['warnings'] += 1 + + # Check for compatility between the .pytxcode and the script + if 'version' not in settings or settings['version'] != data['version']: + print('* PythonTeX warning') + print(' The version of the PythonTeX scripts does not match') + print(' the last code saved by the document--run LaTeX to create') + print(' an updated version. Attempting to proceed.') + sys.stdout.flush() + + # Store all results that haven't already been stored. + data['settings'] = settings + data['pygments_settings'] = pygments_settings + + # Create a tuple of vital quantities that invalidate old saved data + # Don't need to include outputdir, because if that changes, no old output + # fvextfile could be checked on a case-by-case basis, which would result + # in faster output, but that would involve a good bit of additional + # logic, which probably isn't worth it for a feature that will rarely be + # changed. + data['vitals'] = (data['version'], data['encoding'], + settings['gobble'], settings['fvextfile']) + + # Create tuples of vital quantities + data['code_vitals'] = (settings['workingdir'], settings['keeptemps'], + settings['makestderr'], settings['stderrfilename']) + data['cons_vitals'] = (settings['workingdir']) + data['typeset_vitals'] = () + + # Pass any customizations to types + for k in engine_dict: + engine_dict[k].customize(pyfuture=settings['pyfuture'], + pyconfuture=settings['pyconfuture'], + pyconbanner=settings['pyconbanner'], + pyconfilename=settings['pyconfilename']) + + # Store code + # Do this last, so that Pygments settings are available + if pytxcode.startswith('=>PYTHONTEX#'): + gobble = settings['gobble'] + temp_data['pytxcode'] = [Pytxcode(c, gobble) for c in pytxcode.split('=>PYTHONTEX#')[1:]] + else: + temp_data['pytxcode'] = [] + + + + +def get_old_data(data, old_data, temp_data): + ''' + Load data from the last run, if it exists, into the dict old_data. + Determine the path to the PythonTeX scripts, either by using a previously + found, saved path or via kpsewhich. + + The old data is used for determining when PythonTeX has been upgraded, + when any settings have changed, when code has changed (via hashes), and + what files may need to be cleaned up. The location of the PythonTeX + scripts is needed so that they can be imported by the scripts created by + PythonTeX. The location of the scripts is confirmed even if they were + previously located, to make sure that the path is still valid. Finding + the scripts depends on having a TeX installation that includes the + Kpathsea library (TeX Live and MiKTeX, possibly others). + + All code that relies on old_data is written based on the assumption that + if old_data exists and has the current PythonTeX version, then it + contains all needed information. Thus, all code relying on old_data must + check that it was loaded and that it has the current version. If not, + code should adapt gracefully. + ''' + + # Create a string containing the name of the data file + pythontex_data_file = os.path.join(data['settings']['outputdir'], 'pythontex_data.pkl') + + # Load the old data if it exists (read as binary pickle) + if os.path.isfile(pythontex_data_file): + f = open(pythontex_data_file, 'rb') + old = pickle.load(f) + f.close() + # Check for compabilility + if 'vitals' in old and data['vitals'] == old['vitals']: + temp_data['loaded_old_data'] = True + old_data.update(old) + else: + temp_data['loaded_old_data'] = False + # Clean up all old files + if 'files' in old: + for key in old['files']: + for f in old['files'][key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + if 'pygments_files' in old: + for key in old['pygments_files']: + for f in old['pygments_files'][key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + else: + temp_data['loaded_old_data'] = False + + # Set the utilspath + if os.path.isfile(os.path.join(sys.path[0], 'pythontex_utils.py')): + # Need the path with forward slashes, so escaping isn't necessary + data['utilspath'] = sys.path[0].replace('\\', '/') + else: + print('* PythonTeX error') + print(' Could not determine the utils path from sys.path[0]') + print(' The file "pythontex_utils.py" may be missing') + return sys.exit(1) + + + + +def modified_dependencies(key, data, old_data, temp_data): + hashdependencies = temp_data['hashdependencies'] + if key not in old_data['dependencies']: + return False + else: + old_dep_hash_dict = old_data['dependencies'][key] + workingdir = data['settings']['workingdir'] + for dep in old_dep_hash_dict.keys(): + # We need to know if the path is relative (based off the + # working directory) or absolute. We can't use + # os.path.isabs() alone for determining the distinction, + # because we must take into account the possibility of an + # initial ~ (tilde) standing for the home directory. + dep_file = os.path.expanduser(os.path.normcase(dep)) + if not os.path.isabs(dep_file): + dep_file = os.path.join(workingdir, dep_file) + if not os.path.isfile(dep_file): + print('* PythonTeX error') + print(' Cannot find dependency "' + dep + '"') + print(' It belongs to ' + key.replace('#', ':')) + print(' Relative paths to dependencies must be specified from the working directory.') + temp_data['errors'] += 1 + # A removed dependency should trigger an error, but it + # shouldn't cause code to execute. Running the code + # again would just give more errors when it can't find + # the dependency. (There won't be issues when a + # dependency is added or removed, because that would + # involve modifying code, which would trigger + # re-execution.) + elif hashdependencies: + # Read and hash the file in binary. Opening in text mode + # would require an unnecessary decoding and encoding cycle. + f = open(dep_file, 'rb') + hasher = sha1() + hash = hasher(f.read()).hexdigest() + f.close() + if hash != old_dep_hash_dict[dep][1]: + return True + else: + mtime = os.path.getmtime(dep_file) + if mtime != old_dep_hash_dict[dep][0]: + return True + return False + +def should_rerun(hash, old_hash, old_exit_status, key, rerun, data, old_data, temp_data): + # #### Need to clean up arg passing here + if rerun == 'never': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data)): + print('* PythonTeX warning') + print(' Session ' + key.replace('#', ':') + ' has rerun=never') + print(' But its code or dependencies have been modified') + temp_data['warnings'] += 1 + return False + elif rerun == 'modified': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data)): + return True + else: + return False + elif rerun == 'errors': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data) or + old_exit_status[0] != 0): + return True + else: + return False + elif rerun == 'warnings': + if (hash != old_hash or modified_dependencies(key, data, old_data, temp_data) or + old_exit_status != (0, 0)): + return True + else: + return False + elif rerun == 'always': + return True + + + + +def hash_all(data, temp_data, old_data, engine_dict): + ''' + Hash the code to see what has changed and needs to be updated. + + Save the hashes in hashdict. Create update_code, a list of bools + regarding whether code should be executed. Create update_pygments, a + list of bools determining what needs updated Pygments highlighting. + Update pygments_settings to account for Pygments (as opposed to PythonTeX) + commands and environments. + ''' + + # Note that the PythonTeX information that accompanies code must be + # hashed in addition to the code itself; the code could stay the same, + # but its context or args could change, which might require that code be + # executed. All of the PythonTeX information is hashed except for the + # input line number. Context-dependent code is going too far if + # it depends on that. + + # Create variables to more easily access parts of data + pytxcode = temp_data['pytxcode'] + encoding = data['encoding'] + loaded_old_data = temp_data['loaded_old_data'] + rerun = temp_data['rerun'] + pygments_settings = data['pygments_settings'] + + # Calculate cumulative hashes for all code that is executed + # Calculate individual hashes for all code that will be typeset + code_hasher = defaultdict(sha1) + cons_hasher = defaultdict(sha1) + cc_hasher = defaultdict(sha1) + typeset_hasher = defaultdict(sha1) + for c in pytxcode: + if c.is_code: + code_hasher[c.key_run].update(c.hashable_delims_run.encode(encoding)) + code_encoded = c.code.encode(encoding) + code_hasher[c.key_run].update(code_encoded) + if c.is_typeset: + typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding)) + typeset_hasher[c.key_typeset].update(code_encoded) + elif c.is_cons: + cons_hasher[c.key_run].update(c.hashable_delims_run.encode(encoding)) + code_encoded = c.code.encode(encoding) + cons_hasher[c.key_run].update(code_encoded) + if c.is_typeset: + typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding)) + typeset_hasher[c.key_typeset].update(code_encoded) + elif c.is_cc: + cc_hasher[c.cc_type].update(c.hashable_delims_run.encode(encoding)) + cc_hasher[c.cc_type].update(c.code.encode(encoding)) + elif c.is_typeset: + typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding)) + typeset_hasher[c.key_typeset].update(c.code.encode(encoding)) + + + # Store hashes + code_hash_dict = {} + for key in code_hasher: + input_family = key.split('#', 1)[0] + code_hash_dict[key] = (code_hasher[key].hexdigest(), + cc_hasher[input_family].hexdigest(), + engine_dict[input_family].get_hash()) + data['code_hash_dict'] = code_hash_dict + + cons_hash_dict = {} + for key in cons_hasher: + input_family = key.split('#', 1)[0] + cons_hash_dict[key] = (cons_hasher[key].hexdigest(), + cc_hasher[input_family].hexdigest(), + engine_dict[input_family].get_hash()) + data['cons_hash_dict'] = cons_hash_dict + + typeset_hash_dict = {} + for key in typeset_hasher: + typeset_hash_dict[key] = typeset_hasher[key].hexdigest() + data['typeset_hash_dict'] = typeset_hash_dict + + + # See what needs to be updated. + # In the process, copy over macros and files that may be reused. + code_update = {} + cons_update = {} + pygments_update = {} + macros = defaultdict(list) + files = defaultdict(list) + pygments_macros = {} + pygments_files = {} + typeset_cache = {} + dependencies = defaultdict(dict) + exit_status = {} + pygments_settings_changed = {} + if loaded_old_data: + old_macros = old_data['macros'] + old_files = old_data['files'] + old_pygments_macros = old_data['pygments_macros'] + old_pygments_files = old_data['pygments_files'] + old_typeset_cache = old_data['typeset_cache'] + old_dependencies = old_data['dependencies'] + old_exit_status = old_data['exit_status'] + old_code_hash_dict = old_data['code_hash_dict'] + old_cons_hash_dict = old_data['cons_hash_dict'] + old_typeset_hash_dict = old_data['typeset_hash_dict'] + old_pygments_settings = old_data['pygments_settings'] + for s in pygments_settings: + if (s in old_pygments_settings and + pygments_settings[s] == old_pygments_settings[s]): + pygments_settings_changed[s] = False + else: + pygments_settings_changed[s] = True + + # If old data was loaded (and thus is compatible) determine what has + # changed so that only + # modified code may be executed. Otherwise, execute everything. + # We don't have to worry about checking for changes in pyfuture, because + # custom code and default code are hashed. The treatment of keeptemps + # could be made more efficient (if changed to 'none', just delete old temp + # files rather than running everything again), but given that it is + # intended as a debugging aid, that probable isn't worth it. + # We don't have to worry about hashdependencies changing, because if it + # does the hashes won't match (file contents vs. mtime) and thus code will + # be re-executed. + if loaded_old_data and data['code_vitals'] == old_data['code_vitals']: + # Compare the hash values, and set which code needs to be run + for key in code_hash_dict: + if (key in old_code_hash_dict and + not should_rerun(code_hash_dict[key], old_code_hash_dict[key], old_exit_status[key], key, rerun, data, old_data, temp_data)): + code_update[key] = False + macros[key] = old_macros[key] + files[key] = old_files[key] + dependencies[key] = old_dependencies[key] + exit_status[key] = old_exit_status[key] + else: + code_update[key] = True + else: + for key in code_hash_dict: + code_update[key] = True + + if loaded_old_data and data['cons_vitals'] == old_data['cons_vitals']: + # Compare the hash values, and set which code needs to be run + for key in cons_hash_dict: + if (key in old_cons_hash_dict and + not should_rerun(cons_hash_dict[key], old_cons_hash_dict[key], old_exit_status[key], key, rerun, data, old_data, temp_data)): + cons_update[key] = False + macros[key] = old_macros[key] + files[key] = old_files[key] + typeset_cache[key] = old_typeset_cache[key] + dependencies[key] = old_dependencies[key] + exit_status[key] = old_exit_status[key] + else: + cons_update[key] = True + else: + for key in cons_hash_dict: + cons_update[key] = True + + if loaded_old_data and data['typeset_vitals'] == old_data['typeset_vitals']: + for key in typeset_hash_dict: + input_family = key.split('#', 1)[0] + if input_family in pygments_settings: + if (not pygments_settings_changed[input_family] and + key in old_typeset_hash_dict and + typeset_hash_dict[key] == old_typeset_hash_dict[key]): + pygments_update[key] = False + if key in old_pygments_macros: + pygments_macros[key] = old_pygments_macros[key] + if key in old_pygments_files: + pygments_files[key] = old_pygments_files[key] + else: + pygments_update[key] = True + else: + pygments_update[key] = False + # Make sure Pygments styles are up-to-date + pygments_style_list = list(get_all_styles()) + if pygments_style_list != old_data['pygments_style_list']: + pygments_style_defs = {} + # Lazy import + from pygments.formatters import LatexFormatter + for s in pygments_style_list: + formatter = LatexFormatter(style=s, commandprefix='PYG'+s) + pygments_style_defs[s] = formatter.get_style_defs() + else: + pygments_style_defs = old_data['pygments_style_defs'] + else: + for key in typeset_hash_dict: + input_family = key.split('#', 1)[0] + if input_family in pygments_settings: + pygments_update[key] = True + else: + pygments_update[key] = False + # Create Pygments styles + pygments_style_list = list(get_all_styles()) + pygments_style_defs = {} + # Lazy import + from pygments.formatters import LatexFormatter + for s in pygments_style_list: + formatter = LatexFormatter(style=s, commandprefix='PYG'+s) + pygments_style_defs[s] = formatter.get_style_defs() + + # Save to data + temp_data['code_update'] = code_update + temp_data['cons_update'] = cons_update + temp_data['pygments_update'] = pygments_update + data['macros'] = macros + data['files'] = files + data['pygments_macros'] = pygments_macros + data['pygments_style_list'] = pygments_style_list + data['pygments_style_defs'] = pygments_style_defs + data['pygments_files'] = pygments_files + data['typeset_cache'] = typeset_cache + data['dependencies'] = dependencies + data['exit_status'] = exit_status + + + # Clean up for code that will be run again, and for code that no longer + # exists. + if loaded_old_data: + # Take care of code files + for key in code_hash_dict: + if code_update[key] and key in old_files: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + for key in old_code_hash_dict: + if key not in code_hash_dict: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + # Take care of old console files + for key in cons_hash_dict: + if cons_update[key] and key in old_files: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + for key in old_cons_hash_dict: + if key not in cons_hash_dict: + for f in old_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + # Take care of old Pygments files + # The approach here is a little different since there isn't a + # Pygments-specific hash dict, but there is a Pygments-specific + # dict of lists of files. + for key in pygments_update: + if pygments_update[key] and key in old_pygments_files: + for f in old_pygments_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + for key in old_pygments_files: + if key not in pygments_update: + for f in old_pygments_files[key]: + f = os.path.expanduser(os.path.normcase(f)) + if os.path.isfile(f): + os.remove(f) + + + + + +def parse_code_write_scripts(data, temp_data, engine_dict): + ''' + Parse the code file into separate scripts, and write them to file. + ''' + code_dict = defaultdict(list) + cc_dict_begin = defaultdict(list) + cc_dict_end = defaultdict(list) + cons_dict = defaultdict(list) + pygments_list = [] + # Create variables to ease data access + encoding = data['encoding'] + utilspath = data['utilspath'] + outputdir = data['settings']['outputdir'] + workingdir = data['settings']['workingdir'] + pytxcode = temp_data['pytxcode'] + code_update = temp_data['code_update'] + cons_update = temp_data['cons_update'] + pygments_update = temp_data['pygments_update'] + files = data['files'] + # We need to keep track of the last instance for each session, so + # that duplicates can be eliminated. Some LaTeX environments process + # their content multiple times and thus will create duplicates. We + # need to initialize everything at -1, since instances begin at zero. + def negative_one(): + return -1 + last_instance = defaultdict(negative_one) + for c in pytxcode: + if c.input_instance_int > last_instance[c.key_run]: + last_instance[c.key_run] = c.input_instance_int + if c.is_code: + if code_update[c.key_run]: + code_dict[c.key_run].append(c) + if c.is_typeset and pygments_update[c.key_typeset]: + pygments_list.append(c) + elif c.is_cons: + # Only append to Pygments if not run, since Pygments is + # automatically taken care of during run for console content + if cons_update[c.key_run]: + cons_dict[c.key_run].append(c) + elif c.is_typeset and pygments_update[c.key_typeset]: + pygments_list.append(c) + elif (c.is_pyg or c.is_verb) and pygments_update[c.key_typeset]: + pygments_list.append(c) + elif c.is_cc: + if c.cc_pos == 'begin': + cc_dict_begin[c.cc_type].append(c) + else: + cc_dict_end[c.cc_type].append(c) + + # Save + temp_data['code_dict'] = code_dict + temp_data['cc_dict_begin'] = cc_dict_begin + temp_data['cc_dict_end'] = cc_dict_end + temp_data['cons_dict'] = cons_dict + temp_data['pygments_list'] = pygments_list + + # Save the code sessions that need to be updated + # Keep track of the files that are created + # Also accumulate error indices for handling stderr + code_index_dict = {} + for key in code_dict: + input_family, input_session, input_restart = key.split('#') + fname = os.path.join(outputdir, input_family + '_' + input_session + '_' + input_restart + '.' + engine_dict[input_family].extension) + files[key].append(fname) + sessionfile = open(fname, 'w', encoding=encoding) + script, code_index = engine_dict[input_family].get_script(encoding, + utilspath, + workingdir, + cc_dict_begin[input_family], + code_dict[key], + cc_dict_end[input_family]) + for lines in script: + sessionfile.write(lines) + sessionfile.close() + code_index_dict[key] = code_index + temp_data['code_index_dict'] = code_index_dict + + + + +def do_multiprocessing(data, temp_data, old_data, engine_dict): + jobname = data['jobname'] + encoding = data['encoding'] + outputdir = data['settings']['outputdir'] + workingdir = data['settings']['workingdir'] + keeptemps = data['settings']['keeptemps'] + fvextfile = data['settings']['fvextfile'] + pygments_settings = data['pygments_settings'] + verbose = temp_data['verbose'] + + code_dict = temp_data['code_dict'] + cons_dict = temp_data['cons_dict'] + cc_dict_begin = temp_data['cc_dict_begin'] + cc_dict_end = temp_data['cc_dict_end'] + pygments_list = temp_data['pygments_list'] + pygments_style_defs = data['pygments_style_defs'] + + files = data['files'] + macros = data['macros'] + pygments_files = data['pygments_files'] + pygments_macros = data['pygments_macros'] + typeset_cache = data['typeset_cache'] + + errors = temp_data['errors'] + warnings = temp_data['warnings'] + + makestderr = data['settings']['makestderr'] + stderrfilename = data['settings']['stderrfilename'] + code_index_dict = temp_data['code_index_dict'] + + hashdependencies = temp_data['hashdependencies'] + dependencies = data['dependencies'] + exit_status = data['exit_status'] + start_time = data['start_time'] + + + # Set maximum number of concurrent processes for multiprocessing + # Accoding to the docs, cpu_count() may raise an error + try: + max_processes = multiprocessing.cpu_count() + except NotImplementedError: + max_processes = 1 + pool = multiprocessing.Pool(max_processes) + tasks = [] + + # If verbose, print a list of processes + if verbose: + print('\n* PythonTeX will run the following processes:') + + # Add code processes. Note that everything placed in the codedict + # needs to be executed, based on previous testing, except for custom code. + for key in code_dict: + input_family = key.split('#')[0] + # Uncomment the following for debugging, and comment out what follows + '''run_code(encoding, outputdir, workingdir, code_dict[key], + engine_dict[input_family].language, + engine_dict[input_family].command, + engine_dict[input_family].created, + engine_dict[input_family].extension, + makestderr, stderrfilename, + code_index_dict[key], + engine_dict[input_family].errors, + engine_dict[input_family].warnings, + engine_dict[input_family].linenumbers, + engine_dict[input_family].lookbehind, + keeptemps, hashdependencies)''' + tasks.append(pool.apply_async(run_code, [encoding, outputdir, + workingdir, code_dict[key], + engine_dict[input_family].language, + engine_dict[input_family].command, + engine_dict[input_family].created, + engine_dict[input_family].extension, + makestderr, stderrfilename, + code_index_dict[key], + engine_dict[input_family].errors, + engine_dict[input_family].warnings, + engine_dict[input_family].linenumbers, + engine_dict[input_family].lookbehind, + keeptemps, hashdependencies])) + if verbose: + print(' - Code process ' + key.replace('#', ':')) + + # Add console processes + for key in cons_dict: + input_family = key.split('#')[0] + if engine_dict[input_family].language.startswith('python'): + if input_family in pygments_settings: + # Uncomment the following for debugging + '''python_console(jobname, encoding, outputdir, workingdir, + fvextfile, pygments_settings[input_family], + cc_dict_begin[input_family], cons_dict[key], + cc_dict_end[input_family], engine_dict[input_family].startup, + engine_dict[input_family].banner, + engine_dict[input_family].filename)''' + tasks.append(pool.apply_async(python_console, [jobname, encoding, + outputdir, workingdir, + fvextfile, + pygments_settings[input_family], + cc_dict_begin[input_family], + cons_dict[key], + cc_dict_end[input_family], + engine_dict[input_family].startup, + engine_dict[input_family].banner, + engine_dict[input_family].filename])) + else: + tasks.append(pool.apply_async(python_console, [jobname, encoding, + outputdir, workingdir, + fvextfile, + None, + cc_dict_begin[input_family], + cons_dict[key], + cc_dict_end[input_family], + engine_dict[input_family].startup, + engine_dict[input_family].banner, + engine_dict[input_family].filename])) + else: + print('* PythonTeX error') + print(' Currently, non-Python consoles are not supported') + errors += 1 + if verbose: + print(' - Console process ' + key.replace('#', ':')) + + # Add a Pygments process + if pygments_list: + tasks.append(pool.apply_async(do_pygments, [encoding, outputdir, + fvextfile, + pygments_list, + pygments_settings, + typeset_cache])) + if verbose: + print(' - Pygments process') + + # Execute the processes + pool.close() + pool.join() + + # Get the outputs of processes + # Get the files and macros created. Get the number of errors and warnings + # produced. Get any messages returned. Get the exit_status, which is a + # dictionary of code that failed and thus must be run again (its hash is + # set to a null string). Keep track of whether there were any new files, + # so that the last time of file creation in .pytxmcr can be updated. + new_files = False + messages = [] + for task in tasks: + result = task.get() + if result['process'] == 'code': + key = result['key'] + files[key].extend(result['files']) + if result['files']: + new_files = True + macros[key].extend(result['macros']) + dependencies[key] = result['dependencies'] + errors += result['errors'] + warnings += result['warnings'] + exit_status[key] = (result['errors'], result['warnings']) + messages.extend(result['messages']) + elif result['process'] == 'console': + key = result['key'] + files[key].extend(result['files']) + if result['files']: + new_files = True + macros[key].extend(result['macros']) + pygments_files.update(result['pygments_files']) + pygments_macros.update(result['pygments_macros']) + dependencies[key] = result['dependencies'] + typeset_cache[key] = result['typeset_cache'] + errors += result['errors'] + warnings += result['warnings'] + exit_status[key] = (result['errors'], result['warnings']) + messages.extend(result['messages']) + elif result['process'] == 'pygments': + pygments_files.update(result['pygments_files']) + for k in result['pygments_files']: + if result['pygments_files'][k]: + new_files = True + break + pygments_macros.update(result['pygments_macros']) + errors += result['errors'] + warnings += result['warnings'] + messages.extend(result['messages']) + + # Do a quick check to see if any dependencies were modified since the + # beginning of the run. If so, reset them so they will run next time and + # issue a warning + unresolved_dependencies = False + unresolved_sessions= [] + for key in dependencies: + for dep, val in dependencies[key].items(): + if val[0] > start_time: + unresolved_dependencies = True + dependencies[key][dep] = (None, None) + unresolved_sessions.append(key.replace('#', ':')) + if unresolved_dependencies: + print('* PythonTeX warning') + print(' The following have dependencies that have been modified') + print(' Run PythonTeX again to resolve dependencies') + for s in set(unresolved_sessions): + print(' - ' + s) + warnings += 1 + + + # Save all content (only needs to be done if code was indeed run). + # Save a commented-out time corresponding to the last time PythonTeX ran + # and created files, so that tools like latexmk can easily detect when + # another run is needed. + if tasks: + if new_files or not temp_data['loaded_old_data']: + last_new_file_time = start_time + else: + last_new_file_time = old_data['last_new_file_time'] + data['last_new_file_time'] = last_new_file_time + + macro_file = open(os.path.join(outputdir, jobname + '.pytxmcr'), 'w', encoding=encoding) + macro_file.write('%Last time of file creation: ' + str(last_new_file_time) + '\n\n') + for key in macros: + macro_file.write(''.join(macros[key])) + macro_file.close() + + pygments_macro_file = open(os.path.join(outputdir, jobname + '.pytxpyg'), 'w', encoding=encoding) + # Only save Pygments styles that are used + style_set = set([pygments_settings[k]['formatter_options']['style'] for k in pygments_settings if k != ':GLOBAL']) + for key in pygments_style_defs: + if key in style_set: + pygments_macro_file.write(''.join(pygments_style_defs[key])) + for key in pygments_macros: + pygments_macro_file.write(''.join(pygments_macros[key])) + pygments_macro_file.close() + + pythontex_data_file = os.path.join(outputdir, 'pythontex_data.pkl') + f = open(pythontex_data_file, 'wb') + pickle.dump(data, f, -1) + f.close() + + # Print any errors and warnings. + if messages: + print('\n'.join(messages)) + sys.stdout.flush() + # Store errors and warnings back into temp_data + # This is needed because they are ints and thus immutable + temp_data['errors'] = errors + temp_data['warnings'] = warnings + + + + +def run_code(encoding, outputdir, workingdir, code_list, language, command, + command_created, extension, makestderr, stderrfilename, + code_index, errorsig, warningsig, linesig, stderrlookbehind, + keeptemps, hashdependencies): + ''' + Function for multiprocessing code files + ''' + import shlex + + # Create what's needed for storing results + input_family = code_list[0].input_family + input_session = code_list[0].input_session + key_run = code_list[0].key_run + files = [] + macros = [] + dependencies = {} + errors = 0 + warnings = 0 + unknowns = 0 + messages = [] + + # Create message lists only for stderr, one for undelimited stderr and + # one for delimited, so it's easy to keep track of if there is any + # stderr. These are added onto messages at the end. + err_messages_ud = [] + err_messages_d = [] + + # We need to let the user know we are switching code files + # We check at the end to see if there were indeed any errors and warnings + # and if not, clear messages. + messages.append('\n---- Messages for ' + key_run.replace('#', ':') + ' ----') + + # Open files for stdout and stderr, run the code, then close the files + basename = key_run.replace('#', '_') + out_file_name = os.path.join(outputdir, basename + '.out') + err_file_name = os.path.join(outputdir, basename + '.err') + out_file = open(out_file_name, 'w', encoding=encoding) + err_file = open(err_file_name, 'w', encoding=encoding) + # Note that command is a string, which must be converted to list + # Must double-escape any backslashes so that they survive `shlex.split()` + script = os.path.join(outputdir, basename) + # `shlex.split()` only works with Unicode after 2.7.2 + if (sys.version_info.major == 2 and sys.version_info.micro < 3): + exec_cmd = shlex.split(bytes(command.format(file=script.replace('\\', '\\\\')))) + exec_cmd = [unicode(elem) for elem in exec_cmd] + else: + exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\'))) + # Add any created files due to the command + # This needs to be done before attempts to execute, to prevent orphans + for f in command_created: + files.append(f.format(file=script)) + try: + proc = subprocess.Popen(exec_cmd, stdout=out_file, stderr=err_file) + except WindowsError as e: + if e.errno == 2: + # Batch files won't be found when called without extension. They + # would be found if `shell=True`, but then getting the right + # exit code is tricky. So we perform some `cmd` trickery that + # is essentially equivalent to `shell=True`, but gives correct + # exit codes. Note that `subprocess.Popen()` works with strings + # under Windows; a list is not required. + exec_cmd_string = ' '.join(exec_cmd) + exec_cmd_string = 'cmd /C "@echo off & call {0} & if errorlevel 1 exit 1"'.format(exec_cmd_string) + proc = subprocess.Popen(exec_cmd_string, stdout=out_file, stderr=err_file) + else: + raise + + proc.wait() + out_file.close() + err_file.close() + + # Process saved stdout into file(s) that are included in the TeX document. + # + # Go through the saved output line by line, and save any printed content + # to its own file, named based on instance. + # + # The very end of the stdout lists dependencies, if any, so we start by + # removing and processing those. + if not os.path.isfile(out_file_name): + messages.append('* PythonTeX error') + messages.append(' Missing output file for ' + key_run.replace('#', ':')) + errors += 1 + else: + f = open(out_file_name, 'r', encoding=encoding) + out = f.read() + f.close() + try: + out, created = out.rsplit('=>PYTHONTEX:CREATED#\n', 1) + out, deps = out.rsplit('=>PYTHONTEX:DEPENDENCIES#\n', 1) + valid_stdout = True + except: + valid_stdout = False + if proc.returncode == 0: + raise ValueError('Missing "created" and/or "dependencies" delims in stdout; invalid template?') + + if valid_stdout: + # Add created files to created list + for c in created.splitlines(): + files.append(c) + + # Create a set of dependencies, to eliminate duplicates in the event + # that there are any. This is mainly useful when dependencies are + # automatically determined (for example, through redefining open()), + # may be specified multiple times as a result, and are hashed (and + # of a large enough size that hashing time is non-negligible). + deps = set([dep for dep in deps.splitlines()]) + # Process dependencies; get mtimes and (if specified) hashes + for dep in deps: + dep_file = os.path.expanduser(os.path.normcase(dep)) + if not os.path.isabs(dep_file): + dep_file = os.path.join(workingdir, dep_file) + if not os.path.isfile(dep_file): + # If we can't find the file, we return a null hash and issue + # an error. We don't need to change the exit status. If the + # code does depend on the file, there will be a separate + # error when the code attempts to use the file. If the code + # doesn't really depend on the file, then the error will be + # raised again anyway the next time PythonTeX runs when the + # dependency is listed but not found. + dependencies[dep] = (None, None) + messages.append('* PythonTeX error') + messages.append(' Cannot find dependency "' + dep + '"') + messages.append(' It belongs to ' + key_run.replace('#', ':')) + messages.append(' Relative paths to dependencies must be specified from the working directory.') + errors += 1 + elif hashdependencies: + # Read and hash the file in binary. Opening in text mode + # would require an unnecessary decoding and encoding cycle. + hasher = sha1() + f = open(dep_file, 'rb') + hasher.update(f.read()) + f.close() + dependencies[dep] = (os.path.getmtime(dep_file), hasher.hexdigest()) + else: + dependencies[dep] = (os.path.getmtime(dep_file), '') + + for block in out.split('=>PYTHONTEX:STDOUT#')[1:]: + if block: + delims, content = block.split('#\n', 1) + if content: + input_instance, input_command = delims.split('#') + if input_instance.endswith('CC'): + messages.append('* PythonTeX warning') + messages.append(' Custom code for "' + input_family + '" attempted to print or write to stdout') + messages.append(' This is not supported; use a normal code command or environment') + messages.append(' The following content was written:') + messages.append('') + messages.extend([' ' + l for l in content.splitlines()]) + warnings += 1 + elif input_command == 'i': + content = r'\pytx@SVMCR{pytx@MCR@' + key_run.replace('#', '@') + '@' + input_instance + '}\n' + content.rstrip('\n') + '\\endpytx@SVMCR\n\n' + macros.append(content) + else: + fname = os.path.join(outputdir, basename + '_' + input_instance + '.stdout') + f = open(fname, 'w', encoding=encoding) + f.write(content) + f.close() + files.append(fname) + + # Process stderr + if not os.path.isfile(err_file_name): + messages.append('* PythonTeX error') + messages.append(' Missing stderr file for ' + key_run.replace('#', ':')) + errors += 1 + else: + # Open error and code files. + f = open(err_file_name, encoding=encoding) + err = f.readlines() + f.close() + # Divide stderr into an undelimited and a delimited portion + found = False + for n, line in enumerate(err): + if line.startswith('=>PYTHONTEX:STDERR#'): + found = True + err_ud = err[:n] + err_d = err[n:] + break + if not found: + err_ud = err + err_d = [] + # Create a dict for storing any stderr content that will be saved + err_dict = defaultdict(list) + # Create the full basename that will be replaced in stderr + # We need two versions, one with the correct slashes for the OS, + # and one with the opposite slashes. This is needed when a language + # doesn't obey the OS's slash convention in paths given in stderr. + # For example, Windows uses backslashes, but Ruby under Windows uses + # forward in paths given in stderr. + fullbasename_correct = os.path.join(outputdir, basename) + if '\\' in fullbasename_correct: + fullbasename_reslashed = fullbasename_correct.replace('\\', '/') + else: + fullbasename_reslashed = fullbasename_correct.replace('/', '\\') + + if err_ud: + it = iter(code_index.items()) + index_now = next(it) + index_next = index_now + start_errgobble = None + for n, line in enumerate(err_ud): + if basename in line: + # Get the gobbleation. This is used to determine if + # other lines containing the basename are a continuation, + # or separate messages. + errgobble = match('(\s*)', line).groups()[0] + if start_errgobble is None: + start_errgobble = errgobble + # Only issue a message and track down the line numer if + # this is indeed the start of a new message, rather than + # a continuation of an old message that happens to + # contain the basename + if errgobble == start_errgobble: + # Determine the corresponding line number in the document + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + while index_next[1].lines_total < errlinenum: + try: + index_now, index_next = index_next, next(it) + except: + break + if errlinenum > index_now[1].lines_total + index_now[1].lines_input: + doclinenum = str(index_now[1].input_line_int + index_now[1].lines_input) + else: + doclinenum = str(index_now[1].input_line_int + errlinenum - index_now[1].lines_total - 1) + input_file = index_now[1].input_file + else: + doclinenum = '??' + input_file = '??' + + # Try to determine if we are dealing with an error or a + # warning. + found = False + index = n + if stderrlookbehind: + while index >= 0: + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + past_line = err_ud[index] + if (index < n and basename in past_line): + break + for pattern in warningsig: + if pattern in past_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in past_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index -= 1 + else: + while index < len(err_ud): + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + future_line = err_ud[index] + if (index > n and basename in future_line and + future_line.startswith(start_errgobble)): + break + for pattern in warningsig: + if pattern in future_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in future_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index += 1 + # If an error or warning wasn't positively identified, + # increment unknowns. + if not found: + unknowns += 1 + alert_type = 'unknown' + if input_file: + err_messages_ud.append('* PythonTeX stderr - {0} on line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + err_messages_ud.append('* PythonTeX stderr - {0} on line {1}:'.format(alert_type, doclinenum)) + err_messages_ud.append(' ' + line.replace(outputdir, '<outputdir>').rstrip('\n')) + else: + err_messages_ud.append(' ' + line.rstrip('\n')) + + # Create .stderr + if makestderr and err_messages_ud: + process = False + it = iter(code_index.items()) + index_now = next(it) + index_next = index_now + it_last = it + index_now_last = index_now + index_next_last = index_next + err_key_last_int = -1 + for n, line in enumerate(err_ud): + if basename in line: + # Determine the corresponding line number in the document + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + if index_next[1].lines_total >= errlinenum: + it = it_last + index_now = index_now_last + index_next = index_next_last + else: + it_last = it + index_now_last = index_now + index_next_last = index_next + while index_next[1].lines_total < errlinenum: + try: + index_now, index_next = index_next, next(it) + except: + index_now = index_next + break + if index_now[0].endswith('CC'): + process = False + else: + process = True + if len(index_now[1].input_command) > 1: + if errlinenum > index_now[1].lines_total + index_now[1].lines_input: + codelinenum = str(index_now[1].lines_user + index_now[1].lines_input + 1) + else: + codelinenum = str(index_now[1].lines_user + errlinenum - index_now[1].lines_total - index_now[1].inline_count) + else: + codelinenum = '1' + else: + codelinenum = '??' + messages.append('* PythonTeX error') + messages.append(' Line number ' + str(errlinenum) + ' could not be synced with the document') + messages.append(' Content from stderr is not delimited, and cannot be resolved') + errors += 1 + process = False + + if process: + if int(index_now[0]) > err_key_last_int: + err_key = basename + '_' + index_now[0] + err_key_last_int = int(index_now[0]) + line = line.replace(str(errlinenum), str(codelinenum), 1) + if fullbasename_correct in line: + fullbasename = fullbasename_correct + else: + fullbasename = fullbasename_reslashed + if stderrfilename == 'full': + line = line.replace(fullbasename, basename) + elif stderrfilename == 'session': + line = line.replace(fullbasename, input_session) + elif stderrfilename == 'genericfile': + line = line.replace(fullbasename + '.' + extension, '<file>') + elif stderrfilename == 'genericscript': + line = line.replace(fullbasename + '.' + extension, '<script>') + err_dict[err_key].append(line) + elif process: + err_dict[err_key].append(line) + + if err_d: + start_errgobble = None + msg = [] + found_basename = False + for n, line in enumerate(err_d): + if line.startswith('=>PYTHONTEX:STDERR#'): + # Store the last group of messages. Messages + # can't be directly appended to the main list, because + # a PythonTeX message must be inserted at the beginning + # of each chunk of stderr that never references + # the script that was executed. If the script is never + # referenced, then line numbers aren't automatically + # synced. These types of situations are created by + # warnings.warn() etc. + if msg: + if not found_basename: + # Get line number for command or beginning of + # environment + input_instance = last_delim.split('#')[1] + doclinenum = str(code_index[input_instance].input_line_int) + input_file = code_index[input_instance].input_file + # Try to identify alert. We have to parse all + # lines for signs of errors and warnings. This + # may result in overcounting, but it's the best + # we can do--otherwise, we could easily + # undercount, or, finding a warning, miss a + # subsequent error. When this code is actually + # used, it's already a sign that normal parsing + # has failed. + found_error = False + found_warning = False + for l in msg: + for pattern in warningsig: + if pattern in l: + warnings += 1 + found_warning = True + for pattern in errorsig: + if pattern in l: + errors += 1 + found_warning = True + if found_error: + alert_type = 'error' + elif found_warning: + alert_type = 'warning' + else: + unknowns += 1 + alert_type = 'unknown' + if input_file: + err_messages_d.append('* PythonTeX stderr - {0} near line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + err_messages_d.append('* PythonTeX stderr - {0} near line {1}:'.format(alert_type, doclinenum)) + err_messages_d.extend(msg) + msg = [] + found_basename = False + # Never process delimiting info until it is used + # Rather, store the index of the last delimiter + last_delim = line + elif basename in line: + found_basename = True + # Get the gobbleation. This is used to determine if + # other lines containing the basename are a continuation, + # or separate messages. + errgobble = match('(\s*)', line).groups()[0] + if start_errgobble is None: + start_errgobble = errgobble + # Only issue a message and track down the line numer if + # this is indeed the start of a new message, rather than + # a continuation of an old message that happens to + # contain the basename + if errgobble == start_errgobble: + # Determine the corresponding line number in the document + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + # Get info from last delim + input_instance, input_command = last_delim.split('#')[1:-1] + # Calculate the line number in the document + ei = code_index[input_instance] + if errlinenum > ei.lines_total + ei.lines_input: + doclinenum = str(ei.input_line_int + ei.lines_input) + else: + doclinenum = str(ei.input_line_int + errlinenum - ei.lines_total - 1) + input_file = ei.input_file + else: + doclinenum = '??' + input_file = '??' + + # Try to determine if we are dealing with an error or a + # warning. + found = False + index = n + if stderrlookbehind: + while index >= 0: + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + past_line = err_d[index] + if (past_line.startswith('=>PYTHONTEX:STDERR#') or + (index < n and basename in past_line)): + break + for pattern in warningsig: + if pattern in past_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in past_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index -= 1 + else: + while index < len(err_d): + # The order here is important. If a line matches + # both the error and warning patterns, default to + # error. + future_line = err_d[index] + if (future_line.startswith('=>PYTHONTEX:STDERR#') or + (index > n and basename in future_line and future_line.startswith(start_errgobble))): + break + for pattern in warningsig: + if pattern in future_line: + warnings += 1 + alert_type = 'warning' + found = True + break + for pattern in errorsig: + if pattern in future_line: + errors += 1 + alert_type = 'error' + found = True + break + if found: + break + index += 1 + # If an error or warning wasn't positively identified, + # assume error for safety but indicate uncertainty. + if not found: + unknowns += 1 + alert_type = 'unknown' + if input_file: + msg.append('* PythonTeX stderr - {0} on line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + msg.append('* PythonTeX stderr - {0} on line {1}:'.format(alert_type, doclinenum)) + # Clean up the stderr format a little, to keep it compact + line = line.replace(outputdir, '<outputdir>').rstrip('\n') + if '/<outputdir>' in line or '\\<outputdir>' in line: + line = sub(r'(?:(?:[A-Z]:\\)|(?:~?/)).*<outputdir>', '<outputdir>', line) + msg.append(' ' + line) + else: + msg.append(' ' + line.rstrip('\n')) + # Deal with any leftover messages + if msg: + if not found_basename: + # Get line number for command or beginning of + # environment + input_instance = last_delim.split('#')[1] + doclinenum = str(code_index[input_instance].input_line_int) + input_file = code_index[input_instance].input_file + # Try to identify alert. We have to parse all + # lines for signs of errors and warnings. This + # may result in overcounting, but it's the best + # we can do--otherwise, we could easily + # undercount, or, finding a warning, miss a + # subsequent error. When this code is actually + # used, it's already a sign that normal parsing + # has failed. + found_error = False + found_warning = False + for l in msg: + for pattern in warningsig: + if pattern in l: + warnings += 1 + found_warning = True + for pattern in errorsig: + if pattern in l: + errors += 1 + found_warning = True + if found_error: + alert_type = 'error' + elif found_warning: + alert_type = 'warning' + else: + unknowns += 1 + alert_type = 'unknown' + if input_file: + err_messages_d.append('* PythonTeX stderr - {0} near line {1} in "{2}":'.format(alert_type, doclinenum, input_file)) + else: + err_messages_d.append('* PythonTeX stderr - {0} near line {1}:'.format(alert_type, doclinenum)) + err_messages_d.extend(msg) + + # Create .stderr + if makestderr and err_messages_d: + process = False + for n, line in enumerate(err_d): + if line.startswith('=>PYTHONTEX:STDERR#'): + input_instance, input_command = line.split('#')[1:-1] + if input_instance.endswith('CC'): + process = False + else: + process = True + err_key = basename + '_' + input_instance + elif process and basename in line: + found = False + for pattern in linesig: + try: + errlinenum = int(search(pattern, line).groups()[0]) + found = True + break + except: + pass + if found: + # Calculate the line number in the document + # Account for inline + ei = code_index[input_instance] + # Store the `input_instance` in case it's + # incremented later + last_input_instance = input_instance + # If the error or warning was actually triggered + # later on (for example, multiline string with + # missing final delimiter), look ahead and + # determine the correct input_instance, so that + # we get the correct line number. We don't + # associate the created stderr with this later + # instance, however, but rather with the instance + # in which the error began. Doing that might + # possibly be preferable in some cases, but would + # also require that the current stderr be split + # between multiple instances, requiring extra + # parsing. + while errlinenum > ei.lines_total + ei.lines_input: + next_input_instance = str(int(input_instance) + 1) + if next_input_instance in code_index: + next_ei = code_index[next_input_instance] + if errlinenum > next_ei.lines_total: + input_instance = next_input_instance + ei = next_ei + else: + break + else: + break + if len(input_command) > 1: + if errlinenum > ei.lines_total + ei.lines_input: + codelinenum = str(ei.lines_user + ei.lines_input + 1) + else: + codelinenum = str(ei.lines_user + errlinenum - ei.lines_total - ei.inline_count) + else: + codelinenum = '1' + # Reset `input_instance`, in case incremented + input_instance = last_input_instance + else: + codelinenum = '??' + messages.append('* PythonTeX notice') + messages.append(' Line number ' + str(errlinenum) + ' could not be synced with the document') + + line = line.replace(str(errlinenum), str(codelinenum), 1) + if fullbasename_correct in line: + fullbasename = fullbasename_correct + else: + fullbasename = fullbasename_reslashed + if stderrfilename == 'full': + line = line.replace(fullbasename, basename) + elif stderrfilename == 'session': + line = line.replace(fullbasename, input_session) + elif stderrfilename == 'genericfile': + line = line.replace(fullbasename + '.' + extension, '<file>') + elif stderrfilename == 'genericscript': + line = line.replace(fullbasename + '.' + extension, '<script>') + err_dict[err_key].append(line) + elif process: + err_dict[err_key].append(line) + if err_dict: + for err_key in err_dict: + stderr_file_name = os.path.join(outputdir, err_key + '.stderr') + f = open(stderr_file_name, 'w', encoding=encoding) + f.write(''.join(err_dict[err_key])) + f.close() + files.append(stderr_file_name) + + # Clean up temp files, and update the list of existing files + if keeptemps == 'none': + for ext in [extension, 'pytxmcr', 'out', 'err']: + fname = os.path.join(outputdir, basename + '.' + ext) + if os.path.isfile(fname): + os.remove(fname) + elif keeptemps == 'code': + for ext in ['pytxmcr', 'out', 'err']: + fname = os.path.join(outputdir, basename + '.' + ext) + if os.path.isfile(fname): + os.remove(fname) + files.append(os.path.join(outputdir, basename + '.' + extension)) + elif keeptemps == 'all': + for ext in [extension, 'pytxmcr', 'out', 'err']: + files.append(os.path.join(outputdir, basename + '.' + ext)) + + # Take care of any unknowns, based on exit code + # Interpret the exit code as an indicator of whether there were errors, + # and treat unknowns accordingly. This will cause all warnings to be + # misinterpreted as errors if warnings trigger a nonzero exit code. + # It will also cause all warnings to be misinterpreted as errors if there + # is a single error that causes a nonzero exit code. That isn't ideal, + # but shouldn't be a problem, because as soon as the error(s) are fixed, + # the exit code will be zero, and then all unknowns will be interpreted + # as warnings. + if unknowns: + if proc.returncode == 0: + unknowns_type = 'warnings' + warnings += unknowns + else: + unknowns_type = 'errors' + errors += unknowns + unknowns_message = ''' + * PythonTeX notice + {0} message(s) could not be classified + Based on the return code, they were interpreted as {1}''' + messages[0] += textwrap.dedent(unknowns_message.format(unknowns, unknowns_type)) + + # Take care of anything that has escaped detection thus far. + if proc.returncode == 1 and not errors: + errors += 1 + command_message = ''' + * PythonTeX error + An error occurred but no error messages were identified. + This may indicate a bad command or missing program. + The following command was executed: + "{0}"''' + messages[0] += textwrap.dedent(command_message.format(' '.join(exec_cmd))) + + # Add any stderr messages; otherwise, clear the default message header + if err_messages_ud: + messages.extend(err_messages_ud) + if err_messages_d: + messages.extend(err_messages_d) + if len(messages) == 1: + messages = [] + + # Return a dict of dicts of results + return {'process': 'code', + 'key': key_run, + 'files': files, + 'macros': macros, + 'dependencies': dependencies, + 'errors': errors, + 'warnings': warnings, + 'messages': messages} + + + + +def do_pygments(encoding, outputdir, fvextfile, pygments_list, + pygments_settings, typeset_cache): + ''' + Create Pygments content. + + To be run during multiprocessing. + ''' + # Lazy import + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import LatexFormatter + + # Create what's needed for storing results + pygments_files = defaultdict(list) + pygments_macros = defaultdict(list) + errors = 0 + warnings = 0 + messages = [] + messages.append('\n---- Messages for Pygments ----') + + # Create dicts of formatters and lexers. + formatter = dict() + lexer = dict() + for codetype in pygments_settings: + if codetype != ':GLOBAL': + formatter[codetype] = LatexFormatter(**pygments_settings[codetype]['formatter_options']) + lexer[codetype] = get_lexer_by_name(pygments_settings[codetype]['lexer'], + **pygments_settings[codetype]['lexer_options']) + + # Actually parse and highlight the code. + for c in pygments_list: + if c.is_cons: + content = typeset_cache[c.key_run][c.input_instance] + elif c.is_extfile: + if os.path.isfile(c.extfile): + f = open(c.extfile, encoding=encoding) + content = f.read() + f.close() + else: + content = None + messages.append('* PythonTeX error') + messages.append(' Could not find external file ' + c.extfile) + messages.append(' The file was not pygmentized') + else: + content = c.code + processed = highlight(content, lexer[c.input_family], formatter[c.input_family]) + if c.is_inline or content.count('\n') < fvextfile: + # Highlighted code brought in via macros needs SaveVerbatim + processed = sub(r'\\begin{Verbatim}\[(.+)\]', + r'\\begin{{SaveVerbatim}}[\1]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.input_family, c.input_session, c.input_restart, c.input_instance), processed, count=1) + processed = processed.rsplit('\\', 1)[0] + '\\end{SaveVerbatim}\n\n' + pygments_macros[c.key_typeset].append(processed) + else: + fname = os.path.join(outputdir, c.key_typeset.replace('#', '_') + '.pygtex') + f = open(fname, 'w', encoding=encoding) + f.write(processed) + f.close() + pygments_files[c.key_typeset].append(fname) + + if len(messages) == 1: + messages = [] + # Return a dict of dicts of results + return {'process': 'pygments', + 'pygments_files': pygments_files, + 'pygments_macros': pygments_macros, + 'errors': errors, + 'warnings': warnings, + 'messages': messages} + + + + +def python_console(jobname, encoding, outputdir, workingdir, fvextfile, + pygments_settings, cc_begin_list, cons_list, cc_end_list, + startup, banner, filename): + ''' + Use Python's ``code`` module to typeset emulated Python interactive + sessions, optionally highlighting with Pygments. + ''' + # Create what's needed for storing results + key_run = cons_list[0].key_run + files = [] + macros = [] + pygments_files = defaultdict(list) + pygments_macros = defaultdict(list) + typeset_cache = {} + dependencies = {} + errors = 0 + warnings = 0 + messages = [] + messages.append('\n---- Messages for ' + key_run.replace('#', ':') + ' ----') + + # Lazy import what's needed + import code + from collections import deque + if sys.version_info[0] == 2: + # Need a Python 2 interface to io.StringIO that can accept bytes + import io + class StringIO(io.StringIO): + _orig_write = io.StringIO.write + def write(self, s): + self._orig_write(unicode(s)) + else: + from io import StringIO + + # Create a custom console class + class Console(code.InteractiveConsole): + ''' + A subclass of code.InteractiveConsole that takes a list and treats it + as a series of console input. + ''' + + def __init__(self, banner, filename): + if banner == 'none': + self.banner = 'NULL BANNER' + elif banner == 'standard': + cprt = 'Type "help", "copyright", "credits" or "license" for more information.' + self.banner = 'Python {0} on {1}\n{2}'.format(sys.version, sys.platform, cprt) + elif banner == 'pyversion': + self.banner = 'Python ' + '.'.join(str(sys.version_info[n]) for n in (0, 1, 2)) + else: + self.banner = None + if filename == 'console': + self.filename = '<console>' + elif filename == 'stdin': + self.filename = '<stdin>' + else: + self.filename = None + code.InteractiveConsole.__init__(self, filename=self.filename) + self.iostdout = StringIO() + + def consolize(self, startup, cons_list): + self.console_code = deque() + # Delimiters are passed straight through and need newlines + self.console_code.append('=>PYTHONTEX#STARTUP##\n') + cons_config = ''' + import os + import sys + docdir = os.getcwd() + if os.path.isdir('{workingdir}'): + os.chdir('{workingdir}') + if os.getcwd() not in sys.path: + sys.path.append(os.getcwd()) + else: + sys.exit('Cannot find directory {workingdir}') + + if docdir not in sys.path: + sys.path.append(docdir) + + del docdir + ''' + cons_config = cons_config.format(workingdir=workingdir)[1:] + self.console_code.extend(textwrap.dedent(cons_config).splitlines()) + # Code is processed and doesn't need newlines + self.console_code.extend(startup.splitlines()) + for c in cons_list: + self.console_code.append('=>PYTHONTEX#{0}#{1}#\n'.format(c.input_instance, c.input_command)) + self.console_code.extend(c.code.splitlines()) + old_stdout = sys.stdout + sys.stdout = self.iostdout + self.interact(self.banner) + sys.stdout = old_stdout + self.session_log = self.iostdout.getvalue() + + def raw_input(self, prompt): + # Have to do a lot of looping and trying to make sure we get + # something valid to execute + try: + line = self.console_code.popleft() + except IndexError: + raise EOFError + while line.startswith('=>PYTHONTEX#'): + # Get new lines until we get one that doesn't begin with a + # delimiter. Then write the last delimited line. + old_line = line + try: + line = self.console_code.popleft() + self.write(old_line) + except IndexError: + raise EOFError + if line or prompt == sys.ps2: + self.write('{0}{1}\n'.format(prompt, line)) + else: + self.write('\n') + return line + + def write(self, data): + self.iostdout.write(data) + + # Need to combine all custom code and user code to pass to consolize + cons_list = cc_begin_list + cons_list + cc_end_list + # Create a dict for looking up exceptions. This is needed for startup + # commands and for code commands and environments, since their output + # isn't typeset + cons_index = {} + for c in cons_list: + cons_index[c.input_instance] = c.input_line + + # Consolize the code + con = Console(banner, filename) + con.consolize(startup, cons_list) + + # Set up Pygments, if applicable + if pygments_settings is not None: + pygmentize = True + # Lazy import + from pygments import highlight + from pygments.lexers import get_lexer_by_name + from pygments.formatters import LatexFormatter + formatter = LatexFormatter(**pygments_settings['formatter_options']) + lexer = get_lexer_by_name(pygments_settings['lexer'], + **pygments_settings['lexer_options']) + else: + pygmentize = False + + # Process the console output + output = con.session_log.split('=>PYTHONTEX#') + # Extract banner + if banner == 'none': + banner_text = '' + else: + banner_text = output[0] + # Ignore the beginning, because it's the banner + for block in output[1:]: + delims, console_content = block.split('#\n', 1) + if console_content: + input_instance, input_command = delims.split('#') + if input_instance == 'STARTUP': + exception = False + console_content_lines = console_content.splitlines() + for line in console_content_lines: + if (not line.startswith(sys.ps1) and + not line.startswith(sys.ps2) and + line and not line.isspace()): + exception = True + break + if exception: + if 'Error:' in console_content: + errors += 1 + alert_type = 'error' + elif 'Warning:' in console_content: + warnings += 1 + alert_type = 'warning' + else: + errors += 1 + alert_type = 'error (?)' + messages.append('* PythonTeX stderr - {0} in console startup code:'.format(alert_type)) + for line in console_content_lines: + messages.append(' ' + line) + elif input_command in ('c', 'code'): + exception = False + console_content_lines = console_content.splitlines() + for line in console_content_lines: + if (line and not line.startswith(sys.ps1) and + not line.startswith(sys.ps2) and + not line.isspace()): + print('X' + line + 'X') + exception = True + break + if exception: + if 'Error:' in console_content: + errors += 1 + alert_type = 'error' + elif 'Warning:' in console_content: + warnings += 1 + alert_type = 'warning' + else: + errors += 1 + alert_type = 'error (?)' + if input_instance.endswith('CC'): + messages.append('* PythonTeX stderr - {0} near line {1} in custom code for console:'.format(alert_type, cons_index[input_instance])) + else: + messages.append('* PythonTeX stderr - {0} near line {1} in console code:'.format(alert_type, cons_index[input_instance])) + messages.append(' Console code is not typeset, and should have no output') + for line in console_content_lines: + messages.append(' ' + line) + else: + if input_command == 'i': + # Currently, there isn't any error checking for invalid + # content; it is assumed that a single line of commands + # was entered, producing one or more lines of output. + # Given that the current ``\pycon`` command doesn't + # allow line breaks to be written to the .pytxcode, that + # should be a reasonable assumption. + console_content = console_content.split('\n', 1)[1] + if banner_text is not None and input_command == 'console': + # Append banner to first appropriate environment + console_content = banner_text + console_content + banner_text = None + # Cache + key_typeset = key_run + '#' + input_instance + typeset_cache[input_instance] = console_content + # Process for LaTeX + if pygmentize: + processed = highlight(console_content, lexer, formatter) + if console_content.count('\n') < fvextfile: + processed = sub(r'\\begin{Verbatim}\[(.+)\]', + r'\\begin{{SaveVerbatim}}[\1]{{pytx@{0}}}'.format(key_typeset.replace('#', '@')), + processed, count=1) + processed = processed.rsplit('\\', 1)[0] + '\\end{SaveVerbatim}\n\n' + pygments_macros[key_typeset].append(processed) + else: + fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.pygtex') + f = open(fname, 'w', encoding=encoding) + f.write(processed) + f.close() + pygments_files[key_typeset].append(fname) + else: + if console_content.count('\n') < fvextfile: + processed = ('\\begin{{SaveVerbatim}}{{pytx@{0}}}\n'.format(key_typeset.replace('#', '@')) + + console_content + '\\end{SaveVerbatim}\n\n') + macros.append(processed) + else: + processed = ('\\begin{Verbatim}\n' + console_content + + '\\end{Verbatim}\n\n') + fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.tex') + f = open(fname, 'w', encoding=encoding) + f.write(processed) + f.close() + files.append(fname) + + if len(messages) == 1: + messages = [] + + # Return a dict of dicts of results + return {'process': 'console', + 'key': key_run, + 'files': files, + 'macros': macros, + 'pygments_files': pygments_files, + 'pygments_macros': pygments_macros, + 'typeset_cache': typeset_cache, + 'dependencies': dependencies, + 'errors': errors, + 'warnings': warnings, + 'messages': messages} + + + + +def main(): + # Create dictionaries for storing data. + # + # All data that must be saved for subsequent runs is stored in "data". + # (We start off by saving the script version, a global var, in this dict.) + # All data that is only created for this run is stored in "temp_data". + # (We start off by creating keys for keeping track of errors and warnings.) + # All old data will eventually be loaded into "old_data". + # Since dicts are mutable data types, these dicts can be modified + # from within functions, as long as the dicts are passed to the functions. + # For simplicity, variables will often be created within functions to + # refer to dictionary values. + data = {'version': version, 'start_time': time.time()} + temp_data = {'errors': 0, 'warnings': 0} + old_data = dict() + + + # Process command-line options. + # + # This gets the raw_jobname (actual job name), jobname (a sanitized job + # name, used for creating files named after the jobname), and any options. + process_argv(data, temp_data) + # If there aren't errors in argv, and the program is going to run + # (rather than just exit due to --version or --help command-line options), + # print PythonTeX version. Flush to make the message go out immediately, + # so that the user knows PythonTeX has started. + print('This is PythonTeX ' + version) + sys.stdout.flush() + # Once we have the encoding (from argv), we set stdout and stderr to use + # this encoding. Later, we will parse the saved stderr of scripts + # executed via multiprocessing subprocesses, and print the parsed results + # to stdout. The saved stderr uses the same encoding that was used + # for the files that created it (this is important for code containing + # unicode characters), so we also need stdout for the main PythonTeX + # script to support this encoding. Setting stderr encoding is primarily + # a matter of symmetry. Ideally, pythontex*.py will be bug-free, + # and stderr won't be needed! + if sys.version_info[0] == 2: + sys.stdout = codecs.getwriter(data['encoding'])(sys.stdout, 'strict') + sys.stderr = codecs.getwriter(data['encoding'])(sys.stderr, 'strict') + else: + sys.stdout = codecs.getwriter(data['encoding'])(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter(data['encoding'])(sys.stderr.buffer, 'strict') + + + # Load the code and process the settings it passes from the TeX side. + # + # This gets a list containing the code (the part of the code file + # containing the settings is removed) and the processed settings. + load_code_get_settings(data, temp_data) + # Now that the settings are loaded, check if outputdir exits. + # If not, create it. + if not os.path.isdir(data['settings']['outputdir']): + os.mkdir(data['settings']['outputdir']) + + + # Load/create old_data + get_old_data(data, old_data, temp_data) + + + # Hash the code. Determine what needs to be executed. Determine whether + # Pygments should be used. Update pygments_settings to account for + # Pygments commands and environments (as opposed to PythonTeX commands + # and environments). + hash_all(data, temp_data, old_data, engine_dict) + + + # Parse the code and write scripts for execution. + parse_code_write_scripts(data, temp_data, engine_dict) + + + # Execute the code and perform Pygments highlighting via multiprocessing. + do_multiprocessing(data, temp_data, old_data, engine_dict) + + + # Print exit message + print('\n--------------------------------------------------') + # If some rerun settings are used, there may be unresolved errors or + # warnings; if so, print a summary of those along with the current + # error and warning summary + unresolved_errors = 0 + unresolved_warnings = 0 + if temp_data['rerun'] in ('errors', 'modified', 'never'): + global_update = {} + global_update.update(temp_data['code_update']) + global_update.update(temp_data['cons_update']) + for key in data['exit_status']: + if not global_update[key]: + unresolved_errors += data['exit_status'][key][0] + unresolved_warnings += data['exit_status'][key][1] + if unresolved_warnings != 0 or unresolved_errors != 0: + print('PythonTeX: {0}'.format(data['raw_jobname'])) + print(' - Old: {0} error(s), {1} warnings(s)'.format(unresolved_errors, unresolved_warnings)) + print(' - Current: {0} error(s), {1} warnings(s)'.format(temp_data['errors'], temp_data['warnings'])) + else: + print('PythonTeX: {0} - {1} error(s), {2} warning(s)\n'.format(data['raw_jobname'], temp_data['errors'], temp_data['warnings'])) + + # Exit with appropriate exit code based on user settings. + if temp_data['error_exit_code'] and temp_data['errors'] > 0: + sys.exit(1) + else: + sys.exit() + + + +# The "if" statement is needed for multiprocessing under Windows; see the +# multiprocessing documentation. It is also needed in this case when the +# script is invoked via the wrapper. +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py b/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py new file mode 100755 index 00000000000..baff37b411d --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Convert PythonTeX scripts from Python 2 to Python 3 + +It isn't possible to have a single PythonTeX code base, since unicode text +needs to be supported. Under Python 2, this means importing unicode_literals +from __future__, or using the unicode function or "u" prefix. Under Python 3, +all strings are automatically unicode. + +At the same time, the differences between the Python 2 and 3 versions are +usually very small, involving only a few lines of code. To keep the code base +unified, while simultaneously fully supporting both Python 2 and 3, the +following scheme was devised. The code is written for Python 2. Whenever +code is not compatible with Python 3, it is enclosed with the tags +"#// Python 2" and "#\\ End Python 2" (each on its own line, by itself). If +a Python 3 version of the code is needed, it is included between analogous +tags "#// Python 3" and "#\\ End Python 2". The Python 3 code is commented +out with "#", at the same indentation level as the Python 3 tags. + +This script creates Python 3 scripts from the original Python 2 scripts +by commenting out everything between the Python 2 tags, and uncommenting +everything between the Python 3 tags. In this way, full compatibility is +maintained with both Python 2 and 3 while keeping the code base essentially +unified. This approach also allows greater customization of version-specific +code than would be possible if automatic translation with a tool like 2to3 +was required. + +Copyright (c) 2012-2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +from __future__ import unicode_literals +from io import open +import re + + +files_to_process = ('pythontex2.py', 'depythontex2.py') +encoding = 'utf-8' + + +def from2to3(list_of_code): + fixed = [] + in_2 = False + in_3 = False + indent = '' + + for line in list_of_code: + if r'#// Python 2' in line: + in_2 = True + indent = line.split('#', 1)[0] + elif r'#\\ End Python 2' in line: + in_2 = False + elif r'#// Python 3' in line: + in_3 = True + indent = line.split('#', 1)[0] + elif r'#\\ End Python 3' in line: + in_3 = False + elif in_2: + line = re.sub(indent, indent + '#', line, count=1) + elif in_3: + line = re.sub(indent + '#', indent, line, count=1) + fixed.append(line) + return fixed + + +for file in files_to_process: + f = open(file, 'r', encoding=encoding) + converted_code = from2to3(f.readlines()) + f.close() + f = open(re.sub('2', '3', file), 'w', encoding=encoding) + f.write(''.join(converted_code)) + f.close() + + diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_engines.py b/Master/texmf-dist/scripts/pythontex/pythontex_engines.py new file mode 100755 index 00000000000..518820c8a7b --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex_engines.py @@ -0,0 +1,922 @@ +# -*- coding: utf-8 -*- +''' +PythonTeX code engines. + +Provides a class for managing the different languages/types of code +that may be executed. A class instance is created for each language/type of +code. The class provides a method for assembling the scripts that are +executed, combining user code with templates. It also creates the records +needed to synchronize `stderr` with the document. + +Each instance of the class is automatically added to the `engines_dict` upon +creation. Instances are typically accessed via this dictionary. + +The class is called `*CodeEngine` by analogy with a template engine, since it +combines user text (code) with existing templates to produce an output +document (script for execution). + + + +Copyright (c) 2012-2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + +# Imports +import sys +import textwrap +from hashlib import sha1 +from collections import OrderedDict, namedtuple + + +interpreter_dict = {k:k for k in ('python', 'ruby', 'julia')} +# The {file} field needs to be replaced by itself, since the actual +# substitution of the real file can only be done at runtime, whereas the +# substitution for the interpreter should be done when the engine is +# initialized. +interpreter_dict['file'] = '{file}' + + +engine_dict = {} + + +CodeIndex = namedtuple('CodeIndex', ['input_file', 'input_command', + 'input_line_int', 'lines_total', + 'lines_user', 'lines_input', + 'inline_count']) + + +class CodeEngine(object): + ''' + The base class that is used for defining language engines. Each command + and environment family is based on an engine. + + The class assembles the individual scripts that PythonTeX executes, using + templates and user code. It also creates the records needed for + synchronizing `stderr` with the document. + ''' + def __init__(self, name, language, extension, command, template, wrapper, + formatter, errors=None, warnings=None, + linenumbers=None, lookbehind=False, + console=False, startup=None, created=None): + + # Save raw arguments so that they may be reused by subtypes + self._rawargs = (name, language, extension, command, template, wrapper, + formatter, errors, warnings, + linenumbers, lookbehind, console, startup, created) + + # Type check all strings, and make sure everything is Unicode + if sys.version_info[0] == 2: + if (not isinstance(name, basestring) or + not isinstance(language, basestring) or + not isinstance(extension, basestring) or + not isinstance(command, basestring) or + not isinstance(template, basestring) or + not isinstance(wrapper, basestring) or + not isinstance(formatter, basestring)): + raise TypeError('CodeEngine needs string in initialization') + self.name = unicode(name) + self.language = unicode(language) + self.extension = unicode(extension) + self.command = unicode(command) + self.template = unicode(template) + self.wrapper = unicode(wrapper) + self.formatter = unicode(formatter) + else: + if (not isinstance(name, str) or + not isinstance(language, str) or + not isinstance(extension, str) or + not isinstance(command, str) or + not isinstance(template, str) or + not isinstance(wrapper, str) or + not isinstance(formatter, str)): + raise TypeError('CodeEngine needs string in initialization') + self.name = name + self.language = language + self.extension = extension + self.command = command + self.template = template + self.wrapper = wrapper + self.formatter = formatter + # Perform some additional formatting on some strings. Dedent. + # Change from {{ }} tags for replacement fields to { } tags that + # are compatible with Python's string format() method, which is much + # more efficient than a template engine. + self.extension = self.extension.lstrip('.') + self.command = self._dejinja(self.command) + self.template = self._dedent(self._dejinja(self.template)) + self.wrapper = self._dedent(self._dejinja(self.wrapper)) + # Make sure formatter string ends with a newline + if self.formatter.endswith('\n'): + self.formatter = self._dejinja(self.formatter) + else: + self.formatter = self._dejinja(self.formatter) + '\n' + + # Type check errors, warnings, and linenumbers + if errors is None: + errors = [] + else: + if sys.version_info[0] == 2: + if isinstance(errors, basestring): + errors = [errors] + elif not isinstance(errors, list) and not isinstance(errors, tuple): + raise TypeError('CodeEngine needs "errors" to be a string, list, or tuple') + for e in errors: + if not isinstance(e, basestring): + raise TypeError('CodeEngine needs "errors" to contain strings') + errors = [unicode(e) for e in errors] + else: + if isinstance(errors, str): + errors = [errors] + elif not isinstance(errors, list) and not isinstance(errors, tuple): + raise TypeError('CodeEngine needs "errors" to be a string, list, or tuple') + for e in errors: + if not isinstance(e, str): + raise TypeError('CodeEngine needs "errors" to contain strings') + self.errors = errors + if warnings is None: + warnings = [] + else: + if sys.version_info[0] == 2: + if isinstance(warnings, basestring): + warnings = [warnings] + elif not isinstance(warnings, list) and not isinstance(warnings, tuple): + raise TypeError('CodeEngine needs "warnings" to be a string, list, or tuple') + for w in warnings: + if not isinstance(w, basestring): + raise TypeError('CodeEngine needs "warnings" to contain strings') + warnings = [unicode(w) for w in warnings] + else: + if isinstance(warnings, str): + warnings = [warnings] + elif not isinstance(warnings, list) and not isinstance(warnings, tuple): + raise TypeError('CodeEngine needs "warnings" to be a string, list, or tuple') + for w in warnings: + if not isinstance(w, str): + raise TypeError('CodeEngine needs "warnings" to contain strings') + self.warnings = warnings + if linenumbers is None: + linenumbers = 'line {{number}}' + if sys.version_info[0] == 2: + if isinstance(linenumbers, basestring): + linenumbers = [linenumbers] + elif not isinstance(linenumbers, list) and not isinstance(linenumbers, tuple): + raise TypeError('CodeEngine needs "linenumbers" to be a string, list, or tuple') + for l in linenumbers: + if not isinstance(l, basestring): + raise TypeError('CodeEngine needs "linenumbers" to contain strings') + linenumbers = [unicode(l) for l in linenumbers] + else: + if isinstance(linenumbers, str): + linenumbers = [linenumbers] + elif not isinstance(linenumbers, list) and not isinstance(linenumbers, tuple): + raise TypeError('CodeEngine needs "linenumbers" to be a string, list, or tuple') + for l in linenumbers: + if not isinstance(l, str): + raise TypeError('CodeEngine needs "linenumbers" to contain strings') + # Need to replace tags + linenumbers = [l.replace('{{number}}', r'(\d+)') for l in linenumbers] + self.linenumbers = linenumbers + + # Type check lookbehind + if not isinstance(lookbehind, bool): + raise TypeError('CodeEngine needs "lookbehind" to be bool') + self.lookbehind = lookbehind + + # Type check console + if not isinstance(console, bool): + raise TypeError('CodeEngine needs "console" to be bool') + self.console = console + + # Type check startup + if startup is None: + startup = '' + if startup and not self.console: + raise TypeError('PythonTeX can only use "startup" for console types') + else: + if sys.version_info[0] == 2: + if isinstance(startup, basestring): + startup = unicode(startup) + else: + raise TypeError('CodeEngine needs "startup" to be a string') + else: + if not isinstance(startup, str): + raise TypeError('CodeEngine needs "startup" to be a string') + if not startup.endswith('\n'): + startup += '\n' + self.startup = self._dedent(startup) + + # Type check created; make sure it is an iterable and contains Unicode + if created is None: + created = [] + else: + if sys.version_info[0] == 2: + if isinstance(created, basestring): + created = [created] + elif not isinstance(created, list) and not isinstance(created, tuple): + raise TypeError('CodeEngine needs "created" to be a string, list, or tuple') + for f in created: + if not isinstance(f, basestring): + raise TypeError('CodeEngine "created" to contain strings') + created = [unicode(f) for f in created] + else: + if isinstance(created, str): + created = [created] + elif not isinstance(created, list) and not isinstance(created, tuple): + raise TypeError('CodeEngine needs "created" to be a string, list, or tuple') + for f in created: + if not isinstance(f, str): + raise TypeError('CodeEngine needs "created" to contain strings') + self.created = created + + # The base PythonTeX type does not support extend; it is used in + # subtyping. But a dummy extend is needed to fill the extend field + # in templates, if it is provided. + self.extend = '' + + # Create dummy variables for console + self.banner = '' + self.filename = '' + + # Each type needs to add itself to a dict, for later access by name + self._register() + + def _dedent(self, s): + ''' + Dedent and strip leading newlines + ''' + s = textwrap.dedent(s) + while s.startswith('\n'): + s = s[1:] + return s + + def _dejinja(self, s): + ''' + Switch all `{{ }}` tags into `{ }`, and all normal braces `{ }` into + `{{ }}`, so that Python's string format() method may be used. Also + strip any whitespace surrounding the field name. + + This will fail if literal `{{` and `}}` are needed. If those are + ever needed, then options for custom tags will be needed. + ''' + lst = [t.replace('{', '{{') for t in s.split('{{')] + for n in range(1, len(lst)): + lst[n] = lst[n].lstrip(' ') + s = '{'.join(lst) + lst = [t.replace('}', '}}') for t in s.split('}}')] + for n in range(0, len(lst)-1): + lst[n] = lst[n].rstrip(' ') + s = '}'.join(lst) + return s + + def _register(self): + ''' + Add instance to a dict for later access by name + ''' + engine_dict[self.name] = self + + def customize(self, **kwargs): + ''' + Customize the template on the fly. + + This provides customization based on command line arguments + (`--interpreter`) and customization from the TeX side (imports from + `__future__`). Ideally, this function should be restricted to this + and similar cases. The custom code command and environment are + insufficient for such cases, because the command is at a level above + that of code and because of the requirement that imports from + `__future__` be at the very beginning of a script. + ''' + # Take care of `--interpreter` + self.command = self.command.format(**interpreter_dict) + # Take care of `__future__` + if self.language.startswith('python'): + if sys.version_info[0] == 2 and 'pyfuture' in kwargs: + pyfuture = kwargs['pyfuture'] + future_imports = None + if pyfuture == 'all': + future_imports = ''' + from __future__ import absolute_import + from __future__ import division + from __future__ import print_function + from __future__ import unicode_literals + {future}''' + elif pyfuture == 'default': + future_imports = ''' + from __future__ import absolute_import + from __future__ import division + from __future__ import print_function + {future}''' + if future_imports is not None: + future_imports = self._dedent(future_imports) + self.template = self.template.replace('{future}', future_imports) + if self.console: + if sys.version_info[0] == 2 and 'pyconfuture' in kwargs: + pyconfuture = kwargs['pyconfuture'] + future_imports = None + if pyconfuture == 'all': + future_imports = ''' + from __future__ import absolute_import + from __future__ import division + from __future__ import print_function + from __future__ import unicode_literals + ''' + elif pyconfuture == 'default': + future_imports = ''' + from __future__ import absolute_import + from __future__ import division + from __future__ import print_function + ''' + if future_imports is not None: + future_imports = self._dedent(future_imports) + self.startup = future_imports + self.startup + if 'pyconbanner' in kwargs: + self.banner = kwargs['pyconbanner'] + if 'pyconfilename' in kwargs: + self.filename = kwargs['pyconfilename'] + + _hash = None + + def get_hash(self): + ''' + Return a hash of all vital type information (template, etc.). Create + the hash if it doesn't exist, otherwise return a stored hash. + ''' + # This file is encoded in UTF-8, so everything can be encoded in UTF-8. + # It's not important that this encoding be the same as that given by + # the user, since a unique hash is all that's needed. + if self._hash is None: + hasher = sha1() + hasher.update(self.command.encode('utf8')) + hasher.update(self.template.encode('utf8')) + hasher.update(self.wrapper.encode('utf8')) + hasher.update(self.formatter.encode('utf8')) + if self.console: + hasher.update(self.startup.encode('utf8')) + hasher.update(self.banner.encode('utf8')) + hasher.update(self.filename.encode('utf8')) + self._hash = hasher.hexdigest() + return self._hash + + def _process_future(self, code_list): + ''' + Go through a given list of code and extract all imports from + `__future__`, so that they can be relocated to the beginning of the + script. + + The approach isn't foolproof and doesn't support compound statements. + ''' + done = False + future_imports = [] + for n, c in enumerate(code_list): + in_triplequote = False + changed = False + code = c.code.split('\n') + for l, line in enumerate(code): + # Detect __future__ imports + if (line.startswith('from __future__') or + line.startswith('import __future__') and + not in_triplequote): + changed = True + if ';' in line: + raise ValueError('Imports from __future__ should be simple statements; semicolons are not supported') + else: + future_imports.append(line) + code[l] = '' + # Ignore comments, empty lines, and lines with complete docstrings + elif (line.startswith('\n') or line.startswith('#') or + line.isspace() or + ('"""' in line and line.count('"""')%2 == 0) or + ("'''" in line and line.count("'''")%2 == 0)): + pass + # Detect if entering or leaving a docstring + elif line.count('"""')%2 == 1 or line.count("'''")%2 == 1: + in_triplequote = not in_triplequote + # Stop looking for future imports as soon as a non-comment, + # non-empty, non-docstring, non-future import line is found + elif not in_triplequote: + done = True + break + if changed: + code_list[n].code = '\n'.join(code) + if done: + break + if future_imports: + return '\n'.join(future_imports) + else: + return '' + + def _get_future(self, cc_list_begin, code_list): + ''' + Process custom code and user code for imports from `__future__` + ''' + cc_future = self._process_future(cc_list_begin) + code_future = self._process_future(code_list) + if cc_future and code_future: + return cc_future + '\n' + code_future + else: + return cc_future + code_future + + def get_script(self, encoding, utilspath, workingdir, + cc_list_begin, code_list, cc_list_end): + ''' + Assemble the script that will be executed. In the process, assemble + an index of line numbers that may be used to correlate script line + numbers with document line numbers and user code line numbers in the + event of errors or warnings. + ''' + lines_total = 0 + script = [] + code_index = OrderedDict() + + # Take care of future + if self.language.startswith('python'): + future = self._get_future(cc_list_begin, code_list) + else: + future = '' + + # Split template into beginning and ending segments + try: + script_begin, script_end = self.template.split('{body}') + except: + raise ValueError('Template for ' + self.name + ' is missing {{body}}') + + # Add beginning to script + script_begin = script_begin.format(encoding=encoding, future=future, + utilspath=utilspath, workingdir=workingdir, + extend=self.extend, + input_family=code_list[0].input_family, + input_session=code_list[0].input_session, + input_restart=code_list[0].input_restart, + dependencies_delim='=>PYTHONTEX:DEPENDENCIES#', + created_delim='=>PYTHONTEX:CREATED#') + script.append(script_begin) + lines_total += script_begin.count('\n') + + # Prep wrapper + try: + wrapper_begin, wrapper_end = self.wrapper.split('{code}') + except: + raise ValueError('Wrapper for ' + self.name + ' is missing {{code}}') + if not self.language.startswith('python'): + # In the event of a syntax error at the end of user code, Ruby + # (and perhaps others) will use the line number from the NEXT + # line of code that is non-empty, not from the line of code where + # the error started. In these cases, it's important + # to make sure that the line number is triggered immediately + # after user code, so that the line number makes sense. Hence, + # we need to strip all whitespace from the part of the wrapper + # that follows user code. For symetry, we do the same for both + # parts of the wrapper. + wrapper_begin = wrapper_begin.rstrip(' \t\n') + '\n' + wrapper_end = wrapper_end.lstrip(' \t\n') + stdout_delim = '=>PYTHONTEX:STDOUT#{input_instance}#{input_command}#' + stderr_delim = '=>PYTHONTEX:STDERR#{input_instance}#{input_command}#' + wrapper_begin = wrapper_begin.replace('{stdout_delim}', stdout_delim).replace('{stderr_delim}', stderr_delim) + wrapper_begin_offset = wrapper_begin.count('\n') + wrapper_end_offset = wrapper_end.count('\n') + + # Take care of custom code + # Line counters must be reset for cc begin, code, and cc end, since + # all three are separate + lines_user = 0 + inline_count = 0 + for c in cc_list_begin: + # Wrapper before + lines_total += wrapper_begin_offset + script.append(wrapper_begin.format(input_command=c.input_command, + input_context=c.input_context, + input_args=c.input_args_run, + input_instance=c.input_instance, + input_line=c.input_line)) + + # Actual code + lines_input = c.code.count('\n') + code_index[c.input_instance] = CodeIndex(c.input_file, c.input_command, c.input_line_int, lines_total, lines_user, lines_input, inline_count) + script.append(c.code) + if c.is_inline: + inline_count += 1 + lines_total += lines_input + lines_user += lines_input + + # Wrapper after + script.append(wrapper_end) + lines_total += wrapper_end_offset + + # Take care of user code + lines_user = 0 + inline_count = 0 + for c in code_list: + # Wrapper before + lines_total += wrapper_begin_offset + script.append(wrapper_begin.format(input_command=c.input_command, + input_context=c.input_context, + input_args=c.input_args_run, + input_instance=c.input_instance, + input_line=c.input_line)) + + # Actual code + lines_input = c.code.count('\n') + code_index[c.input_instance] = CodeIndex(c.input_file, c.input_command, c.input_line_int, lines_total, lines_user, lines_input, inline_count) + if c.input_command == 'i': + script.append(self.formatter.format(code=c.code.rstrip('\n'))) + inline_count += 1 + else: + script.append(c.code) + lines_total += lines_input + lines_user += lines_input + + # Wrapper after + script.append(wrapper_end) + lines_total += wrapper_end_offset + + # Take care of custom code + lines_user = 0 + inline_count = 0 + for c in cc_list_end: + # Wrapper before + lines_total += wrapper_begin_offset + script.append(wrapper_begin.format(input_command=c.input_command, + input_context=c.input_context, + input_args=c.input_args_run, + input_instance=c.input_instance, + input_line=c.input_line)) + + # Actual code + lines_input = c.code.count('\n') + code_index[c.input_instance] = CodeIndex(c.input_file, c.input_command, c.input_line_int, lines_total, lines_user, lines_input, inline_count) + script.append(c.code) + if c.is_inline: + inline_count += 1 + lines_total += lines_input + lines_user += lines_input + + # Wrapper after + script.append(wrapper_end) + lines_total += wrapper_end_offset + + # Finish script + script.append(script_end) + + return script, code_index + + + + +class SubCodeEngine(CodeEngine): + ''' + Create Engine instances that inherit from existing instances. + ''' + def __init__(self, base, name, language=None, extension=None, command=None, + template=None, wrapper=None, formatter=None, errors=None, + warnings=None, linenumbers=None, lookbehind=False, + console=None, created=None, startup=None, extend=None): + + self._rawargs = (name, language, extension, command, template, wrapper, + formatter, errors, warnings, + linenumbers, lookbehind, console, startup, created) + + base_rawargs = engine_dict[base]._rawargs + args = [] + for n, arg in enumerate(self._rawargs): + if arg is None: + args.append(base_rawargs[n]) + else: + args.append(arg) + + CodeEngine.__init__(self, *args) + + self.extend = engine_dict[base].extend + + if extend is not None: + if sys.version_info[0] == 2: + if not isinstance(extend, basestring): + raise TypeError('PythonTeXSubtype needs a string for "extend"') + extend = unicode(extend) + else: + if not isinstance(extend, str): + raise TypeError('PythonTeXSubtype needs a string for "extend"') + if not extend.endswith('\n'): + extend = extend + '\n' + self.extend += self._dedent(extend) + + + + +class PythonConsoleEngine(CodeEngine): + ''' + This uses the Engine class to store information needed for emulating + Python interactive consoles. + + In the current form, it isn't used as a real engine, but rather as a + convenient storage class that keeps the treatment of all languages/code + types uniform. + ''' + def __init__(self, name, startup=None): + CodeEngine.__init__(self, name=name, language='python', + extension='', command='', template='', + wrapper='', formatter='', errors=None, + warnings=None, linenumbers=None, lookbehind=False, + console=True, startup=startup, created=None) + + + + +python_template = ''' + # -*- coding: {{encoding}} -*- + + {{future}} + + import os + import sys + import codecs + + if sys.version_info[0] == 2: + sys.stdout = codecs.getwriter('{{encoding}}')(sys.stdout, 'strict') + sys.stderr = codecs.getwriter('{{encoding}}')(sys.stderr, 'strict') + else: + sys.stdout = codecs.getwriter('{{encoding}}')(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter('{{encoding}}')(sys.stderr.buffer, 'strict') + + sys.path.append('{{utilspath}}') + from pythontex_utils import PythonTeXUtils + pytex = PythonTeXUtils() + + pytex.docdir = os.getcwd() + if os.path.isdir('{{workingdir}}'): + os.chdir('{{workingdir}}') + if os.getcwd() not in sys.path: + sys.path.append(os.getcwd()) + else: + if len(sys.argv) < 2 or sys.argv[1] != '--manual': + sys.exit('Cannot find directory {{workingdir}}') + if pytex.docdir not in sys.path: + sys.path.append(pytex.docdir) + + {{extend}} + + pytex.input_family = '{{input_family}}' + pytex.input_session = '{{input_session}}' + pytex.input_restart = '{{input_restart}}' + + {{body}} + + pytex.cleanup() + ''' + +python_wrapper = ''' + pytex.input_command = '{{input_command}}' + pytex.input_context = '{{input_context}}' + pytex.input_args = '{{input_args}}' + pytex.input_instance = '{{input_instance}}' + pytex.input_line = '{{input_line}}' + + print('{{stdout_delim}}') + sys.stderr.write('{{stderr_delim}}\\n') + pytex.before() + + {{code}} + + pytex.after() + ''' + + +CodeEngine('python', 'python', '.py', '{{python}} {{file}}.py', + python_template, python_wrapper, 'print(pytex.formatter({{code}}))', + 'Error:', 'Warning:', ['line {{number}}', ':{{number}}:']) + +SubCodeEngine('python', 'py') + +SubCodeEngine('python', 'pylab', extend='from pylab import *') + +sympy_extend = ''' + from sympy import * + pytex.set_formatter('sympy_latex') + ''' + +SubCodeEngine('python', 'sympy', extend=sympy_extend) + + + + +PythonConsoleEngine('pycon') + +PythonConsoleEngine('pylabcon', startup='from pylab import *') + +PythonConsoleEngine('sympycon', startup='from sympy import *') + + + + +ruby_template = ''' + # -*- coding: {{encoding}} -*- + + $stdout.set_encoding('{{encoding}}') + $stderr.set_encoding('{{encoding}}') + + class RubyTeXUtils + attr_accessor :input_family, :input_session, :input_restart, + :input_command, :input_context, :input_args, + :input_instance, :input_line, :dependencies, :created, :docdir + def initialize + @dependencies = Array.new + @created = Array.new + end + def formatter(expr) + return expr.to_s + end + def before + end + def after + end + def add_dependencies(*expr) + self.dependencies.push(*expr) + end + def add_created(*expr) + self.created.push(*expr) + end + def cleanup + puts '{{dependencies_delim}}' + if @dependencies + @dependencies.each { |x| puts x } + end + puts '{{created_delim}}' + if @created + @created.each { |x| puts x } + end + end + end + + rbtex = RubyTeXUtils.new + + rbtex.docdir = Dir.pwd + if File.directory?('{{workingdir}}') + Dir.chdir('{{workingdir}}') + $LOAD_PATH.push(Dir.pwd) unless $LOAD_PATH.include?(Dir.pwd) + elsif ARGV[0] != '--manual' + abort('Cannot change to directory {{workingdir}}') + end + $LOAD_PATH.push(rbtex.docdir) unless $LOAD_PATH.include?(rbtex.docdir) + + {{extend}} + + rbtex.input_family = '{{input_family}}' + rbtex.input_session = '{{input_session}}' + rbtex.input_restart = '{{input_restart}}' + + {{body}} + + rbtex.cleanup + ''' + +ruby_wrapper = ''' + rbtex.input_command = '{{input_command}}' + rbtex.input_context = '{{input_context}}' + rbtex.input_args = '{{input_args}}' + rbtex.input_instance = '{{input_instance}}' + rbtex.input_line = '{{input_line}}' + + puts '{{stdout_delim}}' + $stderr.puts '{{stderr_delim}}' + rbtex.before + + {{code}} + + rbtex.after + ''' + +CodeEngine('ruby', 'ruby', '.rb', '{{ruby}} {{file}}.rb', ruby_template, + ruby_wrapper, 'puts rbtex.formatter({{code}})', + ['Error)', '(Errno', 'error'], 'warning:', ':{{number}}:') + +SubCodeEngine('ruby', 'rb') + + + + +julia_template = ''' + # -*- coding: UTF-8 -*- + + # Currently, Julia only supports UTF-8 + # So can't set stdout and stderr encoding + + type JuliaTeXUtils + input_family::String + input_session::String + input_restart::String + input_command::String + input_context::String + input_args::String + input_instance::String + input_line::String + + _dependencies::Array{String} + _created::Array{String} + docdir::String + + formatter::Function + before::Function + after::Function + add_dependencies::Function + add_created::Function + cleanup::Function + + self::JuliaTeXUtils + + function JuliaTeXUtils() + self = new() + self.self = self + self._dependencies = Array(String, 0) + self._created = Array(String, 0) + + function formatter(expr) + string(expr) + end + self.formatter = formatter + + function null() + end + self.before = null + self.after = null + + function add_dependencies(files...) + for file in files + push!(self._dependencies, file) + end + end + self.add_dependencies = add_dependencies + function add_created(files...) + for file in files + push!(self._created, file) + end + end + self.add_created = add_created + + function cleanup() + println("{{dependencies_delim}}") + for f in self._dependencies + println(f) + end + println("{{created_delim}}") + for f in self._created + println(f) + end + end + self.cleanup = cleanup + + return self + end + end + + jltex = JuliaTeXUtils() + + jltex.docdir = pwd() + try + cd("{{workingdir}}") + if !(contains(LOAD_PATH, pwd())) + push!(LOAD_PATH, pwd()) + end + catch + if !(length(ARGS) > 0 && ARGS[1] == "--manual") + error("Could not find directory {{workingdir}}") + end + end + if !(contains(LOAD_PATH, jltex.docdir)) + push!(LOAD_PATH, jltex.docdir) + end + + {{extend}} + + jltex.input_family = "{{input_family}}" + jltex.input_session = "{{input_session}}" + jltex.input_restart = "{{input_restart}}" + + {{body}} + + jltex.cleanup() + ''' + +julia_wrapper = ''' + jltex.input_command = "{{input_command}}" + jltex.input_context = "{{input_context}}" + jltex.input_args = "{{input_args}}" + jltex.input_instance = "{{input_instance}}" + jltex.input_line = "{{input_line}}" + + println("{{stdout_delim}}") + write(STDERR, "{{stderr_delim}}\\n") + jltex.before() + + {{code}} + + jltex.after() + ''' + +CodeEngine('julia', 'julia', '.jl', '{{julia}} {{file}}.jl', julia_template, + julia_wrapper, 'println(jltex.formatter({{code}}))', + 'ERROR:', 'WARNING:', ':{{number}}', True) + +SubCodeEngine('julia', 'jl') + diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_install_texlive.py b/Master/texmf-dist/scripts/pythontex/pythontex_install_texlive.py new file mode 100755 index 00000000000..28176e8b704 --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex_install_texlive.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Install PythonTeX + +This installation script should work with most TeX distributions. It is +primarily written for TeX Live. It should work with other TeX distributions +that use the Kpathsea library (such as MiKTeX), though with reduced +functionality in some cases. It will require manual input when used with a +distribution that does not include Kpathsea. + +The script will overwrite (and thus update) all previously installed PythonTeX +files. When Kpathsea is available, files may be installed in TEXMFDIST, +TEXMFHOME, or a manually specified location. Otherwise, the installation +location must be specified manually. Installing in TEXMFDIST is useful if +you want to install PythonTeX and then update it in the future from CTAN. +The mktexlsr command is executed at the end of the script, to make the system +aware of any new files. + +The script attempts to create a binary wrapper (Windows) or symlink +(Linux and OS X) for launching the main PythonTeX scripts, pythontex*.py and +depythontex*.py. + + +Copyright (c) 2012-2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +import sys +import platform +from os import path, mkdir, makedirs +if platform.system() != 'Windows': + # Only create symlinks if not under Windows + # (os.symlink doesn't exist under Windows) + from os import symlink, chmod, unlink +from subprocess import call, check_call, check_output +from shutil import copy +import textwrap + + +# We need a version of input that works under both Python 2 and 3 +try: + input = raw_input +except: + pass + + +# Print startup messages and warnings +print('Preparing to install PythonTeX') +if platform.system() != 'Windows': + message = ''' + You may need to run this script with elevated permissions + and/or specify the environment. For example, you may need + "sudo env PATH=$PATH". That is typically necessary when your + system includes a TeX distribution, and you have manually + installed another distribution (common with Ubuntu etc.). If + the installation path you want is not automatically detected, + it may indicate a permissions issue. + ''' + print(textwrap.dedent(message)) + + +# Make sure all necessary files are present +# The pythontex_gallery and pythontex_quickstart are optional; we check for them when installing doc +needed_files = ['pythontex.py', 'pythontex2.py', 'pythontex3.py', + 'pythontex_engines.py', 'pythontex_utils.py', + 'depythontex.py', 'depythontex2.py', 'depythontex3.py', + 'pythontex.sty', 'pythontex.ins', 'pythontex.dtx', + 'pythontex.pdf', 'README'] +missing_files = False +# Print a list of all files that are missing, and exit if any are +for eachfile in needed_files: + if not path.exists(eachfile): + print('Could not find file ' + eachfile) + missing_files = True +if missing_files: + print('Exiting.') + sys.exit(1) + + +# Retrieve the location of valid TeX trees +# Attempt to use kpsewhich; otherwise, resort to manual input +should_exit = False # Can't use sys.exit() in try; will trigger except +try: + if sys.version_info[0] == 2: + texmf_dist = check_output(['kpsewhich', '-var-value', 'TEXMFDIST']).rstrip('\r\n') + texmf_local = check_output(['kpsewhich', '-var-value', 'TEXMFLOCAL']).rstrip('\r\n') + texmf_home = check_output(['kpsewhich', '-var-value', 'TEXMFHOME']).rstrip('\r\n') + else: + texmf_dist = check_output(['kpsewhich', '-var-value', 'TEXMFDIST']).decode('utf-8').rstrip('\r\n') + texmf_local = check_output(['kpsewhich', '-var-value', 'TEXMFLOCAL']).decode('utf-8').rstrip('\r\n') + texmf_home = check_output(['kpsewhich', '-var-value', 'TEXMFHOME']).decode('utf-8').rstrip('\r\n') + message = ''' + Choose an installation location. + + TEXMFDIST is a good choice if you want to update PythonTeX + in the future using your TeX distribution's package manager. + + 1. TEXMFDIST + {0} + 2. TEXMFLOCAL + {1} + 3. TEXMFHOME + {2} + 4. Manual location + '''.format(texmf_dist, texmf_local, texmf_home) + print(textwrap.dedent(message)) + path_choice = input('Installation location (number): ') + if path_choice not in ('1', '2', '3', '4'): + should_exit = True + else: + if path_choice == '1': + texmf_path = texmf_dist + elif path_choice == '2': + texmf_path = texmf_local + elif path_choice == '3': + texmf_path = texmf_home + else: + texmf_path = input('Enter a path:\n') +except: + print('Cannot automatically find TEXMF paths.') + print('kpsewhich does not exist or could not be used.') + texmf_path = input('Please enter a valid installation path:\n') +if should_exit: + sys.exit() +# Make sure path slashes are compatible with the operating system +# Kpathsea returns forward slashes, but Windows needs back slashes +texmf_path = path.expandvars(path.expanduser(path.normcase(texmf_path))) + +# Check to make sure the path is valid +# This is only really needed for manual input +# The '' check is for empty manual input +if texmf_path == '' or not path.exists(texmf_path): + print('Invalid installation path. Exiting.') + sys.exit(1) + + +# Now check that all other needed paths are present +if path_choice != '2': + doc_path = path.join(texmf_path, 'doc', 'latex') + package_path = path.join(texmf_path, 'tex', 'latex') + scripts_path = path.join(texmf_path, 'scripts') + source_path = path.join(texmf_path, 'source', 'latex') +else: + doc_path = path.join(texmf_path, 'doc', 'latex', 'local') + package_path = path.join(texmf_path, 'tex', 'latex', 'local') + scripts_path = path.join(texmf_path, 'scripts', 'local') + source_path = path.join(texmf_path, 'source', 'latex', 'local') +make_paths = False +for eachpath in [doc_path, package_path, scripts_path, source_path]: + if not path.exists(eachpath): + if make_paths: + makedirs(eachpath) + print(' * Created ' + eachpath) + else: + choice = input('Some directories do not exist. Create them? [y/n]\n') + if choice not in ('y', 'n'): + sys.exit('Invalid choice') + elif choice == 'y': + make_paths = True + makedirs(eachpath) + print(' * Created ' + eachpath) + else: + message = ''' + Paths were not created. The following will be needed. + * {0} + * {1} + * {2} + * {3} + + Exiting. + '''.format(doc_path, package_path, scripts_path, source_path) + print(textwrap.dedent(message)) + sys.exit() +# Modify the paths by adding the pythontex directory, which will be created +doc_path = path.join(doc_path, 'pythontex') +package_path = path.join(package_path, 'pythontex') +scripts_path = path.join(scripts_path, 'pythontex') +source_path = path.join(source_path, 'pythontex') + + +# Install files +# Use a try/except in case elevated permissions are needed (Linux and OS X) +print('\nPythonTeX will be installed in \n ' + texmf_path) +try: + # Install docs + if not path.exists(doc_path): + mkdir(doc_path) + copy('pythontex.pdf', doc_path) + copy('README', doc_path) + for doc in ('pythontex_quickstart.tex', 'pythontex_quickstart.pdf', + 'pythontex_gallery.tex', 'pythontex_gallery.pdf'): + if path.isfile(doc): + copy(doc, doc_path) + else: + doc = path.join('..', doc.rsplit('.', 1)[0], doc) + if path.isfile(doc): + copy(doc, doc_path) + # Install package + if not path.exists(package_path): + mkdir(package_path) + copy('pythontex.sty', package_path) + # Install scripts + if not path.exists(scripts_path): + mkdir(scripts_path) + copy('pythontex.py', scripts_path) + copy('depythontex.py', scripts_path) + copy('pythontex_utils.py', scripts_path) + copy('pythontex_engines.py', scripts_path) + for ver in [2, 3]: + copy('pythontex{0}.py'.format(ver), scripts_path) + copy('depythontex{0}.py'.format(ver), scripts_path) + # Install source + if not path.exists(source_path): + mkdir(source_path) + copy('pythontex.ins', source_path) + copy('pythontex.dtx', source_path) +except OSError as e: + if e.errno == 13: + print('Insufficient permission to install PythonTeX') + print('(For example, you may need "sudo", or possibly "sudo env PATH=$PATH")\n') + sys.exit(1) + else: + raise + + +# Install binary wrappers, create symlinks, or suggest the creation of +# wrappers/batch files/symlinks. This part is operating system dependent. +if platform.system() == 'Windows': + # If under Windows, we create a binary wrapper if under TeX Live and + # otherwise alert the user regarding the need for a wrapper or batch file. + + # Assemble the binary path, assuming TeX Live + # The directory bin/ should be at the same level as texmf + bin_path = path.join(path.split(texmf_path)[0], 'bin', 'win32') + if path.exists(path.join(bin_path, 'runscript.exe')): + copy(path.join(bin_path, 'runscript.exe'), path.join(bin_path, 'pythontex.exe')) + copy(path.join(bin_path, 'runscript.exe'), path.join(bin_path, 'depythontex.exe')) + print('\nCreated binary wrapper...') + else: + message = ''' + Could not create a wrapper for launching pythontex.py and + depythontex.py; did not find runscript.exe. You will need + to create a wrapper manually, or use a batch file. Sample + batch files are included with the main PythonTeX files. + The wrapper or batch file should be in a location on the + Windows PATH. The bin/ directory in your TeX distribution + may be a good location. + + The scripts pythontex.py and depythontex.py are located in + the following directory: + {0} + '''.format(scripts_path) + print(textwrap.dedent(message)) +else: + # Optimistically proceed as if every system other than Windows can share + # one set of code. + root_path = path.split(texmf_path)[0] + # Create a list of all possible subdirectories of bin/ for TeX Live + # Source: http://www.tug.org/texlive/doc/texlive-en/texlive-en.html#x1-250003.2.1 + texlive_platforms = ['alpha-linux', 'amd64-freebsd', 'amd64-kfreebsd', + 'armel-linux', 'i386-cygwin', 'i386-freebsd', + 'i386-kfreebsd', 'i386-linux', 'i386-solaris', + 'mips-irix', 'mipsel-linux', 'powerpc-aix', + 'powerpc-linux', 'sparc-solaris', 'universal-darwin', + 'x86_64-darwin', 'x86_64-linux', 'x86_64-solaris'] + symlink_created = False + # Try to create a symlink in the standard TeX Live locations + for pltfrm in texlive_platforms: + bin_path = path.join(root_path, 'bin', pltfrm) + if path.exists(bin_path): + # Create symlink for pythontex*.py + link = path.join(bin_path, 'pythontex.py') + # Unlink any old symlinks if they exist, and create new ones + # Not doing this gave permissions errors under Ubuntu + if path.exists(link): + unlink(link) + symlink(path.join(scripts_path, 'pythontex.py'), link) + chmod(link, 0o775) + # Now repeat for depythontex*.py + link = path.join(bin_path, 'depythontex.py') + if path.exists(link): + unlink(link) + symlink(path.join(scripts_path, 'depythontex.py'), link) + chmod(link, 0o775) + symlink_created = True + + # If the standard TeX Live bin/ locations didn't work, try the typical + # location for MacPorts TeX Live. This should typically be + # /opt/local/bin, but instead of assuming that location, we just climb + # two levels up from texmf-dist and then look for a bin/ directory that + # contains a tex executable. (For MacPorts, texmf-dist should be at + # /opt/local/share/texmf-dist.) + if not symlink_created and platform.system() == 'Darwin': + bin_path = path.join(path.split(root_path)[0], 'bin') + if path.exists(bin_path): + try: + # Make sure this bin/ is the bin/ we're looking for, by + # seeing if pdftex exists + check_output([path.join(bin_path, 'pdftex'), '--version']) + # Create symlinks + link = path.join(bin_path, 'pythontex.py') + if path.exists(link): + unlink(link) + symlink(path.join(scripts_path, 'pythontex.py'), link) + chmod(link, 0o775) + link = path.join(bin_path, 'depythontex.py') + if path.exists(link): + unlink(link) + symlink(path.join(scripts_path, 'depythontex.py'), link) + chmod(link, 0o775) + symlink_created = True + except: + pass + if symlink_created: + print("\nCreated symlink in Tex's bin/ directory...") + else: + print('\nCould not automatically create a symlink to pythontex*.py and depythontex*.py.') + print('You may wish to create one manually, and make it executable via chmod.') + print('The scripts pythontex*.py and depythontex*.py are located in the following directory:') + print(' ' + scripts_path) + + +# Alert TeX to the existence of the package via mktexlsr +try: + print('\nRunning mktexlsr to make TeX aware of new files...') + check_call(['mktexlsr']) +except: + print('Could not run mktexlsr.') + print('Your system may not be aware of newly installed files.') + + +if platform.system() == 'Windows': + # Pause so that the user can see any errors or other messages + # input('\n[Press ENTER to exit]') + print('\n') + call(['pause'], shell=True) diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_utils.py b/Master/texmf-dist/scripts/pythontex/pythontex_utils.py new file mode 100755 index 00000000000..75d3115a395 --- /dev/null +++ b/Master/texmf-dist/scripts/pythontex/pythontex_utils.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- +''' +PythonTeX utilities class for Python scripts. + +The utilities class provides variables and methods for the individual +Python scripts created and executed by PythonTeX. An instance of the class +named "pytex" is automatically created in each individual script. + +Copyright (c) 2012-2013, Geoffrey M. Poore +All rights reserved. +Licensed under the BSD 3-Clause License: + http://www.opensource.org/licenses/BSD-3-Clause + +''' + + +# Imports +import sys +import warnings + +# Most imports are only needed for SymPy; these are brought in via +# "lazy import." Importing unicode_literals here shouldn't ever be necessary +# under Python 2. If unicode_literals is imported in the main script, then +# all strings in this script will be treated as bytes, and the main script +# will try to decode the strings from this script as necessary. The decoding +# shouldn't cause any problems, since all strings in this file may be decoded +# as valid ASCII. (The actual file is encoded in utf-8, but only characters +# within the ASCII subset are actually used). + + +class PythonTeXUtils(object): + ''' + A class of PythonTeX utilities. + + Provides variables for keeping track of TeX-side information, and methods + for formatting and saving data. + + The following variables and methods will be created within instances + of the class during execution. + + String variables for keeping track of TeX information. Most are + actually needed; the rest are included for completeness. + * input_family + * input_session + * input_restart + * input_command + * input_context + * input_args + * input_instance + * input_line + + Future file handle for output that is saved via macros + * macrofile + + Future formatter function that is used to format output + * formatter + ''' + + def __init__(self, fmtr='str'): + ''' + Initialize + ''' + self.set_formatter(fmtr) + + + # We need a context-aware interface to SymPy's latex printer. The + # appearance of typeset math should depend on where it appears in a + # document. (We will refer to the latex printer, rather than the LaTeX + # printer, because the two are separate. Compare sympy.printing.latex + # and sympy.galgebra.latex_ex.) + # + # Creating this interface takes some work. We don't want to import + # anything from SymPy unless it is actually used, to keep things clean and + # fast. + + # First we create a tuple containing all LaTeX math styles. These are + # the contexts that SymPy's latex printer must adapt to. + # The style order doesn't matter, but it corresponds to that of \mathchoice + _sympy_latex_styles = ('display', 'text', 'script', 'scriptscript') + + # Create the public functions for the user, and private functions that + # they call. Two layers are necessary, because we need to be able to + # redefine the functions that do the actual work, once things are + # initialized. But we don't want to redefine the public functions, since + # that could cause problems if the user defines a new function to be one + # of the public functions--the user's function would not change when + # the method was redefined. + def _sympy_latex(self, expr, **settings): + self._init_sympy_latex() + return self._sympy_latex(expr, **settings) + + def sympy_latex(self, expr, **settings): + return self._sympy_latex(expr, **settings) + + def _set_sympy_latex(self, style, **kwargs): + self._init_sympy_latex() + self._set_sympy_latex(style, **kwargs) + + def set_sympy_latex(self, style, **kwargs): + self._set_sympy_latex(style, **kwargs) + # Temporary compatibility with deprecated methods + def init_sympy_latex(self): + warnings.warn('Method init_sympy_latex() is deprecated; init is now automatic.') + self._init_sympy_latex() + + # Next we create a method that initializes the actual context-aware + # interface to SymPy's latex printer. + def _init_sympy_latex(self): + ''' + Initialize a context-aware interface to SymPy's latex printer. + + This consists of creating the dictionary of settings and creating the + sympy_latex method that serves as an interface to SymPy's + LatexPrinter. This last step is actually performed by calling + self._make_sympy_latex(). + ''' + # Create dictionaries of settings for different contexts. + # + # Currently, the main goal is to use pmatrix (or an equivalent) + # in \displaystyle contexts, and smallmatrix in \textstyle, + # \scriptstyle (superscript or subscript), and \scriptscriptstyle + # (superscript or subscript of a superscript or subscript) + # contexts. Basically, we want matrix size to automatically + # scale based on context. It is expected that additional + # customization may prove useful as SymPy's LatexPrinter is + # further developed. + # + # The 'fold_frac_powers' option is probably the main other + # setting that might sometimes be nice to invoke in a + # context-dependent manner. + # + # In the default settings below, all matrices are set to use + # parentheses rather than square brackets. This is largely a + # matter of personal preference. The use of parentheses is based + # on the rationale that parentheses are less easily confused with + # the determinant and are easier to write by hand than are square + # brackets. The settings for 'script' and 'scriptscript' are set + # to those of 'text', since all of these should in general + # require a more compact representation of things. + self._sympy_latex_settings = {'display': {'mat_str': 'pmatrix', 'mat_delim': None}, + 'text': {'mat_str': 'smallmatrix', 'mat_delim': '('}, + 'script': {'mat_str': 'smallmatrix', 'mat_delim': '('}, + 'scriptscript': {'mat_str': 'smallmatrix', 'mat_delim': '('} } + # Now we create a function for updating the settings. + # + # Note that EVERY time the settings are changed, we must call + # self._make_sympy_latex(). This is because the _sympy_latex() + # method is defined based on the settings, and every time the + # settings change, it may need to be redefined. It would be + # possible to define _sympy_latex() so that its definition remained + # constant, simply drawing on the settings. But most common + # combinations of settings allow more efficient versions of + # _sympy_latex() to be defined. + def _set_sympy_latex(style, **kwargs): + if style in self._sympy_latex_styles: + self._sympy_latex_settings[style].update(kwargs) + elif style == 'all': + for s in self._sympy_latex_styles: + self._sympy_latex_settings[s].update(kwargs) + else: + warnings.warn('Unknown LaTeX math style ' + str(style)) + self._make_sympy_latex() + self._set_sympy_latex = _set_sympy_latex + + # Now that the dictionaries of settings have been created, and + # the function for modifying the settings is in place, we are ready + # to create the actual interface. + self._make_sympy_latex() + + # Finally, create the actual interface to SymPy's LatexPrinter + def _make_sympy_latex(self): + ''' + Create a context-aware interface to SymPy's LatexPrinter class. + + This is an interface to the LatexPrinter class, rather than + to the latex function, because the function is simply a + wrapper for accessing the class and because settings may be + passed to the class more easily. + + Context dependence is accomplished via LaTeX's \mathchoice macro. + This macros takes four arguments: + \mathchoice{<display>}{<text>}{<script>}{<scriptscript>} + All four arguments are typeset by LaTeX, and then the appropriate one + is actually typeset in the document based on the current style. This + may seem like a very inefficient way of doing things, but this + approach is necessary because LaTeX doesn't know the math style at a + given point until after ALL mathematics have been typeset. This is + because macros such as \over and \atop change the math style of things + that PRECEDE them. See the following discussion for more information: + http://tex.stackexchange.com/questions/1223/is-there-a-test-for-the-different-styles-inside-maths-mode + + The interface takes optional settings. These optional + settings override the default context-dependent settings. + Accomplishing this mixture of settings requires (deep)copying + the default settings, then updating the copies with the optional + settings. This leaves the default settings intact, with their + original values, for the next usage. + + The interface is created in various ways depending on the specific + combination of context-specific settings. While a general, static + interface could be created, that would involve invoking LatexPrinter + four times, once for each math style. It would also require that + LaTeX process a \mathchoice macro for everything returned by + _sympy_latex(), which would add more inefficiency. In practice, there + will generally be enough overlap between the different settings, and + the settings will be focused enough, that more efficient + implementations of _sympy_latex() are possible. + + Note that we perform a "lazy import" here. We don't want to import + the LatexPrinter unless we are sure to use it, since the import brings + along a number of other dependencies from SymPy. We don't want + unnecessary overhead from SymPy imports. + ''' + # sys has already been imported + import copy + try: + from sympy.printing.latex import LatexPrinter + except ImportError: + sys.exit('Could not import from SymPy') + + # Go through a number of possible scenarios, to create an efficient + # implementation of sympy_latex() + if all(self._sympy_latex_settings[style] == {} for style in self._sympy_latex_styles): + def _sympy_latex(expr, **settings): + ''' + Deal with the case where there are no context-specific + settings. + ''' + return LatexPrinter(settings).doprint(expr) + elif all(self._sympy_latex_settings[style] == self._sympy_latex_settings['display'] for style in self._sympy_latex_styles): + def _sympy_latex(expr, **settings): + ''' + Deal with the case where all settings are identical, and thus + the settings are really only being used to set defaults, + rather than context-specific behavior. + + Check for empty settings, so as to avoid deepcopy + ''' + if not settings: + return LatexPrinter(self._sympy_latex_settings['display']).doprint(expr) + else: + final_settings = copy.deepcopy(self._sympy_latex_settings['display']) + final_settings.update(settings) + return LatexPrinter(final_settings).doprint(expr) + elif all(self._sympy_latex_settings[style] == self._sympy_latex_settings['text'] for style in ('script', 'scriptscript')): + def _sympy_latex(expr, **settings): + ''' + Deal with the case where only 'display' has different settings. + + This should be the most common case. + ''' + if not settings: + display = LatexPrinter(self._sympy_latex_settings['display']).doprint(expr) + text = LatexPrinter(self._sympy_latex_settings['text']).doprint(expr) + else: + display_settings = copy.deepcopy(self._sympy_latex_settings['display']) + display_settings.update(settings) + display = LatexPrinter(display_settings).doprint(expr) + text_settings = copy.deepcopy(self._sympy_latex_settings['text']) + text_settings.update(settings) + text = LatexPrinter(text_settings).doprint(expr) + if display == text: + return display + else: + return r'\mathchoice{' + display + '}{' + text + '}{' + text + '}{' + text + '}' + else: + def _sympy_latex(expr, **settings): + ''' + If all attempts at simplification fail, create the most + general interface. + + The main disadvantage here is that LatexPrinter is invoked + four times and we must create many temporary variables. + ''' + if not settings: + display = LatexPrinter(self._sympy_latex_settings['display']).doprint(expr) + text = LatexPrinter(self._sympy_latex_settings['text']).doprint(expr) + script = LatexPrinter(self._sympy_latex_settings['script']).doprint(expr) + scriptscript = LatexPrinter(self._sympy_latex_settings['scriptscript']).doprint(expr) + else: + display_settings = copy.deepcopy(self._sympy_latex_settings['display']) + display_settings.update(settings) + display = LatexPrinter(display_settings).doprint(expr) + text_settings = copy.deepcopy(self._sympy_latex_settings['text']) + text_settings.update(settings) + text = LatexPrinter(text_settings).doprint(expr) + script_settings = copy.deepcopy(self._sympy_latex_settings['script']) + script_settings.update(settings) + script = LatexPrinter(script_settings).doprint(expr) + scriptscript_settings = copy.deepcopy(self._sympy_latex_settings['scripscript']) + scriptscript_settings.update(settings) + scriptscript = LatexPrinter(scriptscript_settings).doprint(expr) + if display == text and display == script and display == scriptscript: + return display + else: + return r'\mathchoice{' + display + '}{' + text + '}{' + script + '}{' + scriptscript+ '}' + self._sympy_latex = _sympy_latex + + # Now we are ready to create non-SymPy formatters and a method for + # setting formatters + def identity_formatter(self, expr): + ''' + For generality, we need an identity formatter, a formatter that does + nothing to its argument and simply returns it unchanged. + ''' + return expr + + def set_formatter(self, fmtr='str'): + ''' + Set the formatter method. + + This is used to process output that is brought in via macros. It is + also available for the user in formatting printed or saved output. + ''' + if fmtr == 'str': + if sys.version_info[0] == 2: + self.formatter = unicode + else: + self.formatter = str + elif fmtr == 'sympy_latex': + self.formatter = self.sympy_latex + elif fmtr in ('None', 'none', 'identity') or fmtr is None: + self.formatter = self.identity_formatter + else: + raise ValueError('Unsupported formatter type') + + # We need functions that can be executed immediately before and after + # each chunk of code. By default, these should do nothing; they are for + # user customization, or customization via packages. + def before(self): + pass + def after(self): + pass + + + # We need a way to keep track of dependencies + # We create a list that stores specified dependencies, and a method that + # adds dependencies to the list. The contents of this list must be + # written to stdout at the end of the file, to be transmitted back to the + # main script. So we create a method that prints them to stdout. This is + # called via a generic cleanup method that is always invoked at the end of + # the script. + _dependencies = list() + def add_dependencies(self, *args): + self._dependencies.extend(list(args)) + def _save_dependencies(self): + print('=>PYTHONTEX:DEPENDENCIES#') + if self._dependencies: + for dep in self._dependencies: + print(dep) + + # We need a way to keep track of created files, so that they can be + # automatically cleaned up. By default, all files are created within the + # pythontex-files_<jobname> folder, and are thus contained. If a custom + # working directory is used, or files are otherwise created in a custom + # location, it may be desirable to track them and keep them cleaned up. + # Furthermore, even when files are contained in the default directory, it + # may be desirable to delete files when they are no longer needed due to + # program changes, renaming, etc. + _created = list() + def add_created(self, *args): + self._created.extend(list(args)) + def _save_created(self): + print('=>PYTHONTEX:CREATED#') + if self._created: + for creation in self._created: + print(creation) + + def cleanup(self): + self._save_dependencies() + self._save_created() + diff --git a/Master/texmf-dist/source/latex/pythontex/pythontex.bat b/Master/texmf-dist/source/latex/pythontex/pythontex.bat new file mode 100755 index 00000000000..0dfa442b4f7 --- /dev/null +++ b/Master/texmf-dist/source/latex/pythontex/pythontex.bat @@ -0,0 +1,2 @@ +@echo off +pythontex.py %* diff --git a/Master/texmf-dist/source/latex/pythontex/pythontex.dtx b/Master/texmf-dist/source/latex/pythontex/pythontex.dtx new file mode 100644 index 00000000000..a7da1010aeb --- /dev/null +++ b/Master/texmf-dist/source/latex/pythontex/pythontex.dtx @@ -0,0 +1,4085 @@ +% \iffalse meta-comment +% +% Copyright (C) 2012-2013 by Geoffrey M. Poore <gpoore@gmail.com> +% --------------------------------------------------------------------------- +% This work may be distributed and/or modified under the +% conditions of the LaTeX Project Public License, either version 1.3 +% of this license or (at your option) any later version. +% The latest version of this license is in +% http://www.latex-project.org/lppl.txt +% and version 1.3 or later is part of all distributions of LaTeX +% version 2005/12/01 or later. +% +% This work has the LPPL maintenance status `maintained'. +% +% The Current Maintainer of this work is Geoffrey M. Poore. +% +% This work consists of the files pythontex.dtx and pythontex.ins +% and the derived filebase pythontex.sty. +% +% \fi +% +% \iffalse +%<*driver> +\ProvidesFile{pythontex.dtx} +%</driver> +%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01] +%<package>\ProvidesPackage{pythontex} +%<*package> + [2013/08/26 Version~0.12] +%</package> +% +%<*driver> +\documentclass{ltxdoc} +\makeatletter +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{textcomp} +\usepackage[svgnames]{xcolor} +\usepackage{environ} +\usepackage{enumitem} +\usepackage{pythontex} +\usepackage{hyperref} +\hypersetup{colorlinks=true,urlcolor=Green,linkcolor=blue} +\newcommand{\pytxtodo}[1]{} +%\newcommand{\pytxtodo}[1]{\textcolor{red}{TO~DO: \scantokens{#1}}} +\newcommand{\changestext}{} +\NewEnviron{changelog}[2]{% + \g@addto@macro\changestext{\item[#1] (#2) \begin{itemize}}% + \expandafter\g@addto@macro\expandafter\changestext\expandafter{\BODY}% + \g@addto@macro\changestext{\end{itemize}}% +} +\newcommand{\PrintChangelog}{% + \addcontentsline{toc}{section}{Version History} + \section*{Version History}% + \begin{description}% + \changestext + \end{description}% +} +\EnableCrossrefs +\CodelineIndex +%\RecordChanges +\makeatother +\begin{document} + \DocInput{pythontex.dtx} + %\PrintChanges + \PrintIndex +\end{document} +%</driver> +% \fi +% +% \CheckSum{2928} +% +% \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 +% Lower-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 +% Digits \0\1\2\3\4\5\6\7\8\9 +% Exclamation \! Double quote \" Hash (number) \# +% Dollar \$ Percent \% Ampersand \& +% Acute accent \' Left paren \( Right paren \) +% Asterisk \* Plus \+ Comma \, +% Minus \- Point \. Solidus \/ +% Colon \: Semicolon \; Less than \< +% Equals \= Greater than \> Question mark \? +% Commercial at \@ Left bracket \[ Backslash \\ +% Right bracket \] Circumflex \^ Underscore \_ +% Grave accent \` Left brace \{ Vertical bar \| +% Right brace \} Tilde \~} +% +% +% +% \begin{changelog}{v0.12}{2013/08/26} +% \item Added support for the Julia language, with the \texttt{julia} and \texttt{jl} families of commands and environments. (Note that Pygments only added Julia support in version 1.6.) +% \item Warnings and errors are now synchronized with the line numbers of files brought in via \texttt{\string\input}, \texttt{\string\include}, etc. This is accomplished using the \texttt{currfile} package. +% \item Added package option \texttt{gobble}. When \texttt{gobble=auto}, all code is dedented before being executed and/or typeset. The current implementation is functional but basic; it will be improved and extended in the future. +% \item The document root directory is now always added to \texttt{sys.path} (or its equivalent), even when it is not the working directory. (The working directory has been added to \texttt{sys.path} since v0.12beta.) The document directory is added after the working directory, so that the working directory has precedence. +% \item Fixed a bug in \texttt{console} commands and environments; \texttt{sys.path} now contains the working and document directories, and the working directory is now the output directory by default. This parallels the behavior of non-\texttt{console} commands and environments. +% \item Added command-line option \texttt{-{}-interpreter} that allows an interpreter to be invoked via a specific command. This allows, for example, a specific version of Python to be invoked. +% \item Improved synchronization of stderr in cases when an error is triggered far after its origin (for example, an error caused by a multiline string that is lacking a closing quote/delimiter, and thus may span several chunks of user code). +% \item Modified usage of the \texttt{shlex} module to work around its lack of Unicode support in Python versions prior to 2.7.3. +% \item Fixed a bug from v0.12beta that prevented \texttt{\string\inputpygments} from working when \texttt{pygments=true}. +% \item Fixed a bug with counters that caused errors when content spanning multiple columns was created within a \texttt{tabular} environment. +% \item Added checking for compatible Python versions in \texttt{pythontex.py}. +% \item Improved execution of \texttt{*.bat} and \texttt{*.cmd} files under Windows. The solution from v0.12beta allowed \texttt{*.bat} and \texttt{*.cmd} to be found and executed when the extension was not given, but did not give correct return codes. +% \end{changelog} +% +% \begin{changelog}{v0.12beta}{2013/06/24} +% \item Merged \texttt{pythontex\_types*.py} into a single replacement \texttt{pythontex\_engines.py} compatible with both Python 2 and 3. It is now much simpler to add support for additional languages. +% \item Added support for the Ruby language as a demonstration of new capabilities. The \texttt{ruby} and \texttt{rb} families of commands and environments may be enabled via the new \texttt{usefamily} package option. Support for additional languages is coming soon. See the new section in the documentation on support for other languages for more information. +% \item Reimplemented treatment of Pygments content for better efficiency. Now a Pygments process only runs if there is content to highlight. Eliminated redundant highlighting of unmodified code. +% \item Improved treatment of dependencies. If a dependency is modified (\texttt{os.path.getmtime()}) after the current PythonTeX run starts, then code that depends on it will be re-executed the next time PythonTeX runs. A message is also issued to indicate that this is the case. +% \item The utilities class now has \texttt{before()} and \texttt{after()} methods that are called immediately before and after user code. These may be redefined to customize output. For example, LaTeX commands could be printed before and after user code; stdout could be redirected to \texttt{StringIO} for further processing; or matplotlib figures could be automatically detected, saved, and included in the document. +% \item Added explanation of how to track dependencies and created files automatically, and how to include matplotlib figures automatically, to the documentation for the PythonTeX utilities class. +% \item Created a new system for parsing and synchronizing stderr. +% \begin{itemize} +% \item Exceptions that do not reference a line number in user code (such as those from \texttt{warnings.warn()} in a module) are now traced back to a single command or environment. Previously no synchronization was attempted. This is accomplished by writing delimiters to stderr before executing the code from each command/environment. +% \item Exceptions that do reference a line in user code are more efficiently synchronized with a document line number. This is accomplished by careful record keeping as each script is assembled. Line number synchronization no longer involves parsing the script that was executed. +% \item Improved and generalized parsing of stderr, in preparation for supporting additional languages. Exceptions that cannot be identified as errors or warnings are treated based on \texttt{Popen.returncode}. +% \end{itemize} +% \item Created a new system for \texttt{console} content. +% \begin{itemize} +% \item There are now separate families of \texttt{console} commands and environments. No Pygments or \texttt{fancyvrb} settings are shared with the non-\texttt{console} families, as was previously the case. There is a new family of commands and environments based on \texttt{pycon}, including the \texttt{\string\pycon} command (inline reference to console variable), \texttt{pyconsole} environment (same as the old one), \texttt{\string\pyconc} and \texttt{pyconcode} (execute only), and \texttt{\string\pyconv} and \texttt{pyconverbatim} (typeset only). There are equivalent families based on \texttt{pylabcon} and \texttt{sympycon}. +% \item Each console session now runs in its own process and is cached individually. Console output is now cached so that changing Pygments settings no longer requires re-execution. +% \item Unicode is now supported under Python 2. +% \item The new package option \texttt{pyconfuture} allows automatic imports from \texttt{\_\_future\_\_} for \texttt{console} families under Python 2, paralleling the \texttt{pyfuture} option. +% \item Any errors or warnings caused by code that is not typeset (\texttt{code} command and environment, startup code) are reported in the run summary. This ensures that such code does not create mischief. +% \item \texttt{customcode} is now supported for \texttt{console} content. +% \end{itemize} +% \item Better support for \texttt{latexmk} and similar build tools. PythonTeX creates a file of macros (\texttt{*.pytxmcr}) that is always included in a document, and thus can be automatically detected and tracked by \texttt{latexmk}. This file now contains the time at which PythonTeX last created files. When new files are created, the macro file will have a new hash, triggering another document compile. +% \item Improved the way in which the PythonTeX \texttt{outputdir} is added to the graphics path. This had been done with \texttt{\string\graphicspath}, but that overwrites any graphics path previously specified by the user. Now the \texttt{outputdir} is appended to any pre-existing path. +% \item Added the \texttt{depythontex} option \texttt{-{}-graphicspath}. This adds the \texttt{outputdir} to the graphics path of the \texttt{depythontex} document. +% \item The installer now provides more options for installation locations. It will now create missing directories if desired. +% \item The working directory (\texttt{workingdir}) is now appended to \texttt{sys.path}, so that code there may be imported. +% \item Under Windows, \texttt{subprocess.Popen()} is now invoked with \texttt{shell=True} if \texttt{shell=False} results in a WindowsError. This allows commands involving \texttt{*.bat} and \texttt{*.cmd} files to be executed when the extension is not specified; otherwise, only \texttt{*.exe} can be found and run. +% \item The path to utils is now found in \texttt{pythontex.py} via \texttt{sys.path[0]} rather than \texttt{kpsewhich}. This allows the PythonTeX scripts to be executed in an arbitrary location; they no longer must be installed in a texmf tree where \texttt{kpsewhich} can find them. +% \item Added \texttt{rerun} value \texttt{never}. +% \item At the end of each run, data and macros are only saved if modified, improving efficiency. +% \item The number of temporary files required by each process was reduced by one. All macros for commands like \texttt{\string\py} are now returned within stdout, rather than in their own file. +% \item Fixed a bug with \texttt{\string\stderrpythontex}; it was defaulting to \texttt{verb} rather than \texttt{verbatim} mode. +% \end{changelog} +% +% \begin{changelog}{v0.11}{2013/04/21} +% \item As the first non-beta release, this version adds several features and introduces several changes. You should read these release notes carefully, since some changes are not backwards-compatible. Changes are based on a thorough review of all current and planned features. PythonTeX's capabilities have already grown beyond what was originally intended, and a long list of features still remains to be implemented. As a result, some changes are needed to ensure consistent syntax and naming in the future. Insofar as possible, all command names and syntax will be frozen after this release. +% \item Added the \texttt{pythontex.py} and \texttt{depythontex.py} wrapper scripts. When run, these detect the current version of Python and import the correct PythonTeX code. It is still possible to run \texttt{pythontex*.py} and \texttt{depythontex*.py} directly, but the new wrapper scripts should be used instead for simplicity. There is now only a single \texttt{pythontex\_utils.py}, which works with both Python 2 and Python 3. +% \item Added the \texttt{beta} package option. This makes the current version behave like v0.11beta, for compatibility. This option is temporary and will probably only be retained for a few releases. +% \item Backward-incompatible changes (require the \texttt{beta} option to restore old behavior) +% \begin{itemize} +% \item The \texttt{pyverb} environment has been renamed \texttt{pyverbatim}. The old name was intended to be concise, but promoted confusion with LaTeX's \texttt{\string\verb} macro. +% \item For \texttt{\string\printpythontex}, \texttt{\string\stdoutpythontex}, and \texttt{\string\stderrpythontex}, the modes \texttt{inlineverb} and \texttt{v} have been replaced by \texttt{verb}, and the old mode \texttt{verb} has been replaced by \texttt{verbatim}. This brings naming conventions in line with standard LaTeX \texttt{\string\verb} and \texttt{verbatim}, avoiding a source of potential confusion. +% \item The \texttt{\string\setpythontexpyglexer}, \texttt{\string\setpythontexpygopt}, and \texttt{\string\setpygmentspygopt} commands now take an optional argument and a mandatory argument, rather than two mandatory arguments. This creates better uniformity among current and planned settings macros. +% \item The \texttt{\string\setpythontexformatter} and \texttt{\string\setpygmentsformatter} commands have been replaced by the \texttt{\string\setpythontexprettyprinter} and \texttt{\string\setpygmentsprettyprinter} commands. This anticipates possible upcoming features. It also avoids potential confusion with Pygments's formatters and the utilities class's \texttt{formatter()} method. +% \end{itemize} +% \item Deprecated (still work, but raise warnings; after a few releases, they will raise errors instead, and after that eventually be removed) +% \begin{itemize} +% \item The \texttt{rerun} setting \texttt{all} was renamed \texttt{always}, in preparation for upcoming features. +% \item The \texttt{stderr} option is replaced by \texttt{makestderr}. The \texttt{print}/\texttt{stdout} option is replaced by \texttt{debug}. These are intended to prevent confusion with future features. +% \item The \texttt{fixlr} option is deprecated. It was originally introduced to deal with some of SymPy's LaTeX formatting, which has since changed. +% \item The utilities class method \texttt{init\_sympy\_latex()} is deprecated. The \texttt{sympy\_latex()} and \texttt{set\_sympy\_latex()} methods now automatically initialize themselves on first use. +% \end{itemize} +% \item Added \texttt{autostdout} package option and \texttt{\string\setpythontexautostdout}, to complement \texttt{autoprint}. Added |prettyprinter| and |prettyprintinline| package options to complement new settings commands. +% \item Added quickstart guide. +% \item Installer now installs gallery and quickstart files, if present. +% \end{changelog} +% +% \begin{changelog}{v0.11beta}{2013/02/17} +% \item Commands like \texttt{\string\py} can now bring in any valid LaTeX code, including verbatim content, under the pdfTeX and XeTeX engines. Verbatim content was not allowed previously. LuaTeX cannot bring in verbatim, due to a known bug. +% \item Added package option \texttt{depythontex} and scripts \texttt{depythontex*.py}. These allow a PythonTeX document to be converted into a pure LaTeX document, with no Python dependency. The package option creates an auxiliary file with extension \texttt{.depytx}. The \texttt{depythontex*.py} scripts take this auxiliary file and the original LaTeX document, and combine the two to produce a new document that does not rely on the PythonTeX package. All PythonTeX commands and environments are replaced by their output. All Python-generated content is substituted directly into the document. By default, all typeset code is wrapped in \texttt{\string\verb} and \texttt{verbatim}, but \texttt{depythontex*.py} has a \texttt{-{}-listing} option that allows \texttt{fancyvrb}, \texttt{listings}, \texttt{minted}, or \texttt{pythontex} to be used instead. +% \item The current PythonTeX version is now saved in the \texttt{.pytxcode}. If this does not match the version of the PythonTeX scripts, a warning is issued. This makes it easier to determine errors due to version mismatches. +% \item Fixed an incompatibility with the latest release of \texttt{xstring} (version 1.7, 2013/01/13). +% \item Fixed a bug in the \texttt{console} environment that could cause problems when switching from Pygments highlighting to \texttt{fancyvrb} when using the \texttt{fvextfile} option. Fixed a bug introduced in the v0.10beta series that prevented the \texttt{console} environment from working with \texttt{fancyvrb}. +% \item Fixed a bug with PythonTeX verbatim commands and environments that use Pygments. The verbatim commands and environments were incorrectly treated as if they had the attributes of executed code in the v0.10beta series. +% \item Fixed a bug from the v0.10beta series that sometimes prevented imports from \texttt{\_\_future\_\_} from working when there were multiple sessions. +% \item Fixed a bug related to hashing dependencies' mtime under Python 3. +% \end{changelog} +% +% \begin{changelog}{v0.10beta2}{2013/01/23} +% \item Improved \texttt{pythontex*.py}'s handling of the name of the file being processed. A warning is no longer raised if the name is given with an extension; extensions are now processed (stripped) automatically. The filename may now contain a path to the file, so you need not run \texttt{pythontex*.py} from within the document's directory. +% \item Added command-line option \texttt{-{}-verbose} for more verbose output. Currently, this prints a list of all processes that are launched. +% \item Fixed a bug that could crash \texttt{pythontex*.py} when the package option \texttt{pygments=false}. +% \item Added documentation about \texttt{autoprint} behavior in the preamble. Summary: \texttt{code} commands and environments are allowed in the preamble as of v0.10beta. \texttt{autoprint} only applies to the body of the document, because nothing can be typeset in the preamble. Content printed in the preamble can be brought in by explicitly using \texttt{\string\printpythontex}, but this should be used with great care. +% \item Revised \texttt{\string\stdoutpythontex} and \texttt{\string\printpythontex} so that they work in the preamble. Again, this should be used with great care if at all. +% \item Revised treatment of any content that custom code attempts to print. Custom code is not allowed to print to the document (see documentation). If custom code attempts to print, a warning is raised, and the printed content is included in the \texttt{pythontex*.py} run summary. +% \item One-line entries in stderr, such as those produced by Python's \texttt{warnings.warn()}, were not previously parsed because they are of the form \texttt{:<linenumber>:} rather than \texttt{line~<linenumber>}. These are now parsed and synchronized with the document. They are also correctly parsed for inclusion in the document via \texttt{\string\stderrpythontex}. +% \item If the package option \texttt{stderrfilename} is changed, all sessions that produced errors or warnings are now re-executed automatically, so that their stderr content is properly updated with the new filename. +% \end{changelog} +% +% \begin{changelog}{v0.10beta}{2013/01/09} +% \item Backward-incompatible: Redid treatment of command-line options for \texttt{pythontex*.py}, using Python's \texttt{argparse} module. Run \texttt{pythontex*.py} with option \texttt{-h} to see new command line options. +% \item Deprecated: \texttt{\string\setpythontexcustomcode} is deprecated in favor of the \texttt{\string\pythontexcustomc} command and \texttt{pythontexcustomcode} environment. These allow entry of pure code, unlike \texttt{\string\setpythontexcustomcode}. These also allow custom code to be added to the beginning or end of a session, via an optional argument. Improved treatment of errors and warnings associated with custom code. +% \item The summary of errors and warnings now correctly differentiates errors and warnings produced by user code, rather than treating all of them as errors. By default, \texttt{pythontex*.py} now returns an exit code of 1 if there were errors. +% \item The PythonTeX utilities class now allows external file dependencies to be specified via \texttt{pytex.add\_dependencies()}, so that sessions are automatically re-executed when external dependencies are modified (modification is determined via either hash or mtime; this is governed by the new \texttt{hashdependencies} option). +% \item The PythonTeX utilities class now allows created files to be specified via \texttt{pytex.add\_created()}, so that created files may be automatically cleaned up (deleted) when the code that created them is modified (for example, name change for a saved plot). +% \item Added the following package options. +% \begin{itemize} +% \item \texttt{stdout} (or \texttt{print}): Allows input of stdout to be disabled. Useful for debugging. +% \item \texttt{runall}: Executes everything. Useful when code depends on external data. +% \item \texttt{rerun}: Determines when code is re-executed. Code may be set to always run (same as \texttt{runall} option), or only run when it is modified or when it produces errors or warnings. By default, code is always re-executed if there are errors or modifications, but not re-executed if there are warnings. +% \item \texttt{hashdependencies}: Determines whether external dependencies (data, external code files highlighted with Pygments, etc.) are checked for modification via hashing or modification time. Modification time is default for performance reasons. +% \end{itemize} +% \item Added the following new command line options. The options that are equivalent to package options are overridden by the package options when present. +% \begin{itemize} +% \item \texttt{-{}-error-exit-code}: Determines whether an exit code of 1 is returned if there were errors. On by default, but can be turned off since it is undesirable when working with some editors. +% \item \texttt{-{}-runall}: Equivalent to new package option. +% \item \texttt{-{}-rerun}: Equivalent to new package option. +% \item \texttt{-{}-hashdependencies}: Equivalent to new package option. +% \end{itemize} +% \item Modified the \texttt{fixlr} option, so that it only patches commands if they have not already been patched (avoids package conflicts). +% \item Added \texttt{\string\setpythontexautoprint} command for toggling autoprint on/off within the body of the document. +% \item Installer now attempts to create symlinks under OS X and Linux with TeX Live, and under OS X with MacPorts Tex Live. +% \item Performed compatibility testing under lualatex and xelatex (previously, had only tested with pdflatex). Added documentation for using these TeX engines; at most, slightly different preambles are needed. Modified the PythonTeX gallery to support all three engines. +% \item Code commands and environments may now be used in the preamble. This, combined with the new treatment of custom code, allows PythonTeX to be used in creating LaTeX packages. +% \item Added documentation for using PythonTeX in LaTeX programming. +% \item Fixed a bug that sometimes caused incorrect line numbers with \texttt{stderr} content. Improved processing of stderr. +% \item Fixed a bug in automatic detection of pre-existing listings environment. +% \item Improved the detection of imports from \texttt{\_\_future\_\_}. Detection should now be stricter, faster, and more accurate. +% \end{changelog} +% +% \begin{changelog}{v0.9beta3}{2012/07/17} +% \item Added Unicode support, which required the Python code to be split into one set for Python 2 and another set for Python 3. This will require any old installation to be completely removed, and a new installation created from scratch. +% \item Refactoring of Python code. Documents should automatically re-execute all code after updating to the new version. Otherwise, you should delete the PythonTeX directory and run PythonTeX. +% \item Improved installation script. +% \item Added package options: pyfuture, stderr, upquote, pyglexer, pyginline. Renamed the pygextfile option to fvextfile. +% \item Added custom code and workingdir commands. +% \item Added the console environment and associated options. +% \item Rewrote pythontex\_utils*.py, creating a new, context-aware interface to SymPy's LatexPrinter class. +% \item Content brought in via macros no longer uses labels. Rather, long defs are used, which allows line breaks. +% \item Pygments highlighting is now default for PythonTeX commands and environments +% \end{changelog} +% +% \begin{changelog}{v0.9beta2}{2012/05/09} +% \item Changed Python output extension to .stdout. +% \end{changelog} +% +% \begin{changelog}{v0.9beta}{2012/04/27} +% \item Initial public beta release. +% \end{changelog} +% +% +% \DoNotIndex{\newcommand,\newenvironment} +% \DoNotIndex{\#,\$,\%,\&,\@,\\,\{,\},\^,\_,\~,\ } +% \DoNotIndex{\@ne} +% \DoNotIndex{\advance,\begingroup,\catcode,\closein} +% \DoNotIndex{\closeout,\day,\def,\edef,\else,\empty,\endgroup} +% \DoNotIndex{\begin,\end,\bgroup,\egroup} +% +% \providecommand*{\url}{\texttt} +% \newcommand{\pytex}{Python\TeX} +% \GetFileInfo{pythontex.dtx} +% \title{\vspace{-0.5in}The \textsf{pythontex} package} +% \author{Geoffrey M.\ Poore \\ \url{gpoore@gmail.com} \\ \href{https://github.com/gpoore/pythontex}{\texttt{github.com/gpoore/pythontex}}} +% \date{\fileversion~from \filedate} +% +% \maketitle +% +% \begin{abstract} +% \pytex\ provides access to Python from within \LaTeX\ documents. It allows Python code entered within a \LaTeX\ document to be executed, and the results to be included within the original document. Python code may be adjacent to the figure or calculation it produces. The package also makes possible macro definitions that mix Python and \LaTeX\ code. In addition, \pytex\ provides syntax highlighting for many programming languages via the Pygments syntax highlighter. +% +% \pytex\ is fast and user-friendly. Python code is only executed when it has been modified, or when user-specified criteria are met. When code is executed, user-defined sessions automatically run in parallel. If Python code produces errors, the error message line numbers are synchronized with the \LaTeX\ document line numbering, simplifying debugging. Dependencies may be specified so that code is automatically re-executed whenever they are modified. +% +% Because documents that use \pytex\ mix \LaTeX\ and Python code, they are less suitable than plain \LaTeX\ documents for journal submission, sharing, and conversion to other formats. \pytex\ includes a |depythontex| utility that creates a copy of a document in which all \pytex\ content is replaced by its output. +% +% While Python is the focus of \pytex, adding basic support for an additional language is usually as simple as creating a new class instance and a few templates, usually totaling less than 100 lines of code. The following languages are already have built-in support: Ruby. +% \end{abstract} +% +% +% \section*{\centering Warning} +% +% \pytex\ makes possible some pretty amazing things. But that power brings with it a certain risk and responsibility. Compiling a document that uses \pytex\ involves executing Python code, and potentially other programs, on your computer. You should only compile \pytex\ documents from sources you trust. \pytex\ comes with NO WARRANTY.\footnote{All \LaTeX\ code is licensed under the \href{http://www.latex-project.org/lppl.txt}{\LaTeX\ Project Public License (LPPL)} and all Python code is licensed under the \href{http://www.opensource.org/licenses/BSD-3-Clause}{BSD 3-Clause License}.} The copyright holder and any additional authors will not be liable for any damages. +% +% +% \pagebreak +% \tableofcontents +% \pagebreak +% +% +% \section{Introduction} +% +% This introduction provides background and objectives for the \pytex\ package. To jump right in and get started, you may wish to consult the |pythontex_quickstart| and |pythontex_gallery| documents, as well as Sections~\ref{sec:installing-and-running} and \ref{sec:usage}, below. If you are primarily interested in using \pytex\ with a language other than Python, see Section~\ref{sec:other-languages}. +% +% \LaTeX\ can do a lot,\footnote{\TeX\ is a Turing-complete language.} but the programming required can sometimes be painful.\footnote{As I learned in creating this package.} In spite of the many packages available for \LaTeX, the libraries and packages of a general-purpose programming language are lacking. Furthermore, it can be convenient to include non-\LaTeX\ code in a document to make it more reproducible. For these reasons, there have been multiple systems that allow other languages to be used within \LaTeX\ documents.\footnote{I am not including the various web and weave dialects in my discussion, since they typically involve a web or weave document from which the .tex source is generated, and thus weaker integration with \LaTeX. Two sophisticated examples of this approach are \href{http://www.stat.uni-muenchen.de/~leisch/Sweave/}{Sweave} and \href{http://yihui.name/knitr/}{knitr}, both of which combine \LaTeX\ with the R language for tasks such as dynamic report generation.} +% \begin{itemize} +% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/perltex/}{Perl\TeX} allows the bodies of \LaTeX\ macros to be written in Perl. +% \item \href{http://www.ctan.org/tex-archive/macros/latex/contrib/sagetex/}{Sage\TeX} allows code for the Sage mathematics software to be executed from within a \LaTeX\ document. +% \item Martin R.\ Ehmsen's \href{http://www.ctan.org/pkg/python}{|python.sty|} provides a very basic method of executing Python code from within a \LaTeX\ document. +% \item \href{http://elec.otago.ac.nz/w/index.php/SympyTeX}{Sympy\TeX} allows more sophisticated Python execution, and is largely based on a subset of Sage\TeX. +% \item \href{http://www.luatex.org/}{Lua\TeX} extends the pdf\TeX\ engine to provide Lua as an embedded scripting language, and as a result yields tight, low-level Lua integration. +% \end{itemize} +% +% \pytex\ attempts to fill a perceived gap in the current integrations of \LaTeX\ with an additional language. It has a number of objectives, only some of which have been met by previous packages. +% \begin{description} +% \item[Execution speed]\hfill\\ In the approaches mentioned above, all the non-\LaTeX\ code is executed at every compilation of the \LaTeX\ document (Perl\TeX, Lua\TeX, and |python.sty|), or all the non-\LaTeX\ code is executed every time it is modified (Sage\TeX\ and Sympy\TeX). However, many tasks such as plotting and data analysis take a significant time to execute. We need a way to fine-tune code execution, so that independent blocks of slow code may be separated into their own sessions and are only executed when modified. If we are going to split code into multiple sessions, we might as well run these sessions in parallel, further increasing speed. A byproduct of this approach is that it now becomes much more feasible to include slower code, since we can still have fast compilations whenever the slow code isn't modified. +% \item[Compiling without executing]\hfill\\ Even with all of these features to boost execution speed, there will be times when we have to run slow code. Thus, we need the execution of non-\LaTeX\ code to be separated from compiling the \LaTeX\ document. We need to be able to edit and compile a document containing unexecuted code. Unexecuted code should be invisible or be replaced by placeholders. Sage\TeX\ and Sympy\TeX\ have implemented such a separation of compiling and executing. In contrast, Lua\TeX\ and Perl\TeX\ execute all the code at each compilation---but that is appropriate given their goal of simplifying macro programming. +% \item[Error messages]\hfill\\ Whenever code is saved from a \LaTeX\ document to an external file and then executed, the line numbers for any error messages will not correspond to the line numbering of the original \LaTeX\ document. At one extreme, |python.sty| doesn't attempt to deal with this issue, while at the other extreme, Sage\TeX\ uses an ingenous system of |Try|/|Except| statements on every chunk of code. We need a system that translates all error messages so that they correspond to the line numbering of the original \LaTeX\ document, with minimal overhead when there are no errors. +% \item[Syntax highlighting]\hfill\\ Once we begin using non-\LaTeX\ code, sooner or later we will want to typeset some of it, which means we need syntax highlighting. A number of syntax highlighting packages currently exist for \LaTeX; perhaps the most popular are |listings| and |minted|. |listings| uses pure \LaTeX. It has not been updated since 2007, which makes it a less ideal solution in some circumstances. |minted| uses the Python-based syntax highlighter Pygments to perform highlighting. Pygments can provide superior syntax highlighting, but |minted| can be very slow because all code must be highlighted at each compilation and each instance of highlighting involves launching an external Python process. We need high-speed, user-friendly syntax highlighting via Pygments.\footnote{The author recently started maintaining the \texttt{minted} package. In the near future, \texttt{minted} will inherit \pytex's speed enhancements, and the two packages will become more compatible.} +% \item[Printing]\hfill\\ It would be nice for the |print| statement/function,\footnote{In Python, |print| was a statement until Python 3, when it became a function. The function form is available via import from |\textunderscore\textunderscore future\textunderscore\textunderscore| in Python 2.6 and later.} or its equivalent, to automatically return its output within the \LaTeX\ document. For example, using |python.sty| it is possible to generate some text while in Python, open a file, save the text to it, close the file, and then |\input| the file after returning to \LaTeX. But it is much simpler to generate the text and |print| it, since the printed content is automatically included in the \LaTeX\ document. This was one of the things that |python.sty| really got right. +% \item[Pure code]\hfill\\ \LaTeX\ has a number of special characters (|#| \texttt{\$} |%| |&| |~| |_| |^| |\| |{| |}|), which complicates the entry of non-\LaTeX\ code since these same characters are common in many languages. Sage\TeX\ and Sympy\TeX\ delimit all inline code with curly braces (|{}|), but this approach fails in the (somewhat unlikely) event that code needs to contain an unmatched brace. More seriously, they do not allow the percent symbol |%| (modular arithmetic and string formatting in Sage and Python) to be used within inline code. Rather, a |\percent| macro must be used instead. This means that code must (sometimes) be entered as a hybrid between \LaTeX\ and the non-\LaTeX\ language. Lua\TeX\ is somewhat similar: ``The main thing about Lua code in a TeX document is this: the code is expanded by TeX before Lua gets to it. This means that all the Lua code, even the comments, must be valid TeX!''\footnote{\url{http://wiki.contextgarden.net/Programming_in_LuaTeX}} In the case of Lua\TeX, though, there is the \href{http://www.ctan.org/pkg/luacode}{\texttt{luacode}} package that allows for pure Lua. +% +% This language hybridization is not terribly difficult to work around in the Sage\TeX\ and Sympy\TeX\ cases, and is actually a Lua\TeX\ feature in many contexts. But if we are going to create a system for general-purpose access to a non-\LaTeX\ language, we need \textbf{all} valid code to work correctly in \textbf{all} contexts, with no hybridization of any sort required. We should be able to copy and paste valid code into a \LaTeX\ document, without having to worry about hybridizing it. Among other things, this means that inline code delimiters other than \LaTeX's default curly braces |{}| must be available. +% \item[Hybrid code]\hfill\\ Although we need a system that allows input of pure non-\LaTeX\ code, it would also be convenient to allow hybrid code, or code in which \LaTeX\ macros may be present and are expanded before the code is executed. This allows \LaTeX\ data to be easily passed to the non-\LaTeX\ language, facilitating a tighter integration of the two languages and the use of the non-\LaTeX\ language in macro definitions. +% \item[Math and science libraries]\hfill\\ The author decided to create \pytex\ after writing a physics dissertation using \LaTeX\ and realizing how frustrating it can be to switch back and forth between a \TeX\ editor and plotting software when fine-tuning figures. We need access to a non-\LaTeX\ language like Python, MATLAB, or Mathematica that provides strong support for data analysis and visualization. To maintain broad appeal, this language should primarily involve open-source tools, should have strong cross-platform support, and should also be suitable for general-purpose programming. +% \item[Language-independent implementation]\hfill\\ It would be nice to have a system for executing non-\LaTeX\ code that depends very little on the language of the code. We should not expect to escape all language dependence. But if the system is designed to be as general as possible, then it may be expanded in the future to support additional languages. +% \end{description} +% +% Python was chosen as the language to fulfill these objectives for several reasons. +% \begin{itemize} +% \item It is open-source and has good cross-platform support. +% \item It has a strong set of scientific, numeric, and visualization packages, including \href{http://numpy.scipy.org/}{NumPy}, \href{http://www.scipy.org/}{SciPy}, \href{http://matplotlib.sourceforge.net/}{matplotlib}, and \href{http://sympy.org}{SymPy}. Much of the initial motivation for \pytex\ was the ability to create publication-quality plots and perform complex mathematical calculations without having to leave the \TeX\ editor. +% \item We need a language that is suitable for scripting. Lua is already available via Lua\TeX, and in any case lacks the math and science tools.\footnote{One could use \href{http://labix.org/lunatic-python}{Lunatic Python}, and some numeric packages for Lua are \href{http://numlua.luaforge.net/}{in development}.} Perl is already available via Perl\TeX, although Perl\TeX's emphasis on Perl for macro creation makes it rather unsuitable for scientific work using the \href{http://pdl.perl.org/}{Perl Data Language (PDL)} or for more general programming. Python is one logical choice for scripting. +% \end{itemize} +% +% Now at this point there will almost certainly be some reader, sooner or later, who wants to object, ``But what about language \textit{X}!'' Well, yes, in some respects the choice to use Python did come down to personal preference. But you should give Python a try, if you haven't already. You may also wish to consider the many interfaces that are available between Python and other languages. If you still aren't satisfied, keep in mind \pytex's ``language-independent'' implementation! In many cases, adding support for additional languages is relatively simple (see Section~\ref{sec:other-languages}). +% +% +% +% \section{Installing and running} +% \label{sec:installing-and-running} +% +% \subsection{Installing \pytex} +% +% \pytex\ requires a \TeX\ installation. It has been tested with \href{http://www.tug.org/texlive/}{\TeX\ Live} and \href{http://miktex.org/}{MiK\TeX}, but should work with other distributions. The following \LaTeX\ packages, with their dependencies, are required: |fancyvrb|, |etex|, |etoolbox|, |xstring|, |pgfopts|, |newfloat|, |currfile|, and |color| or |xcolor|. A current \TeX\ installation is recommended, since some features require recent versions of the packages. If you are creating and including graphics, you will also need |graphicx|. The \href{http://www.ctan.org/pkg/mdframed}{\texttt{mdframed}} package is recommended for enclosing typeset code in boxes with fancy borders and/or background colors; \href{http://www.ctan.org/pkg/tcolorbox}{\texttt{tcolorbox}} and \href{http://www.ctan.org/pkg/framed}{\texttt{framed}} are alternatives. +% +% \pytex\ also requires a \href{http://www.python.org/}{Python} installation. Python 2.7 is recommended for the greatest compatibility with scientific tools, although many scientific packages are now compatible with Python 3. \pytex\ is compatible with Python 2.7 and 3.2+. The Python package \href{http://pygments.org/}{Pygments} must be installed for syntax highlighting to function. \pytex\ has been tested with Pygments 1.4 and later, but the latest version is recommended. For scientific work, or to compile |pythontex_gallery.tex|, the following are also recommended: \href{http://numpy.scipy.org/}{NumPy}, \href{http://www.scipy.org/}{SciPy}, \href{http://matplotlib.sourceforge.net/}{matplotlib}, and \href{http://sympy.org}{SymPy}. +% +% \pytex\ also provides support for other languages such as Ruby, so you will need to install any additional languags you plan to use. Typically, the most recent major version of these languages is supported. +% +% \pytex\ consists of the following files: +% \begin{itemize} +% \item Installer file |pythontex.ins| +% \item Documented \LaTeX\ source file |pythontex.dtx|, from which |pythontex.pdf| and |pythontex.sty| are generated +% \item Main script |pythontex.py|, which imports from |pythontex2.py| or |pythontex3.py|, based on the Python version +% \item Language definitions |pythontex_engines.py| +% \item Utilities class |pythontex_utils.py| +% \item |depythontex.py|, which imports from |depythontex2.py| or |depythontex3.py|, based on the Python version; used to remove \pytex\ dependence +% \item |README| (in rst style) +% \item |pythontex_gallery.tex| and |pythontex_gallery.pdf| +% \item |pythontex_quickstart.tex| and |pythontex_quickstart.pdf| +% \item Optional installation script |pythontex_install_texlive.py| for \TeX\ Live +% \item Optional batch file |pythontex.bat| for use in launching |pythontex.py| under Windows +% \item Optional conversion script |pythontex_2to3.py| for converting \pytex\ code written for Python 2 into a form compatible with Python 3 +% \end{itemize} +% The style file |pythontex.sty| may be generated by running \LaTeX\ on |pythontex.ins|. The documentation you are reading may be generated by running \LaTeX\ on |pythontex.dtx|. Some code is provided in two forms, one for Python 2 and one for Python 3 (names ending in |2| and |3|). Whenever this is the case, a version-independent wrapper is supplied that automatically runs the correct code based on the Python version. For example, there are two main scripts, |pythontex2.py| and |pythontex3.py|, but you should actually run |pythontex.py|, which imports the correct code based on the Python version.\footnote{Unfortunately, it is not possible to provide full Unicode support for both Python 2 and 3 using a single script. Currently, all code is written for Python 2, and then the Python 3 version is automatically generated via the |pythontex\_2to3.py| script. This script comments out code that is only for Python 2, and un-comments code that is only for Python 3.} +% +% If you want the absolute latest version of \pytex, you should install it manually from \href{https://github.com/gpoore/pythontex}{github.com/gpoore/pythontex}. A Python installation script is provided for use with \TeX\ Live. It has been tested with Windows, Linux, and OS~X, but may need manual input or slight modifications depending on your system. The installation script performs the steps described below. \textbf{Note that for a typical \TeX\ setup under Linux, you may need to run the script with elevated privileges, and may need to run it with the user's PATH.} This can be necessary when you are using a Linux distribution that includes an outdated version of \TeX\ Live, and have installed a new version manually. \textbf{If you are installing \pytex\ on a machine with multiple version of \TeX, make sure you install \pytex\ for the correct version.} For example, under Ubuntu Linux, you will probably need the following command if you have installed the latest version of \TeX\ Live manually: +% \begin{verbatim} +% sudo env PATH=$PATH python pythontex_install_texlive.py +% \end{verbatim} +% +% The installer creates the following files. It will offer to create the paths if they do not exist. If you are installing in TEXMFLOCAL, the paths will have an additional |local/| at the end. +% \begin{itemize} +% \item \meta{\TeX\ tree root}|/doc/latex/pythontex/| +% \begin{itemize} +% \item |pythontex.pdf| +% \item |README| +% \item |pythontex_quickstart.tex| +% \item |pythontex_quickstart.pdf| +% \item |pythontex_gallery.tex| +% \item |pythontex_gallery.pdf| +% \end{itemize} +% \item \meta{\TeX\ tree root}|/scripts/pythontex/| +% \begin{itemize} +% \item |pythontex.py|, |pythontex2.py| and |pythontex3.py| +% \item |pythontex_engines.py| +% \item |pythontex_utils.py| +% \item |depythontex.py|, |depythontex2.py| and |depythontex3.py| +% \end{itemize} +% \item \meta{\TeX\ tree root}|/source/latex/pythontex/| +% \begin{itemize} +% \item |pythontex.dtx| +% \item |pythontex.ins| +% \end{itemize} +% \item \meta{\TeX\ tree root}|/tex/latex/pythontex/| +% \begin{itemize} +% \item |pythontex.sty| +% \end{itemize} +% \end{itemize} +% After the files are installed, the system must be made aware of their existence. The installer runs |mktexlsr| to do this. In order for |pythontex.py| and |depythontex.py| to be executable, a symlink (\TeX\ Live under Linux), launching wrapper (\TeX\ Live under Windows), or batch file (general Windows) should be created in the |bin/|\meta{system} directory. The installer attempts to create a symlink or launching wrapper automatically. For \TeX\ Live under Windows, it copies |bin/win32/runscript.exe| to |bin/win32/pythontex.exe| to create the wrapper.\footnote{See the output of |runscript -h| under Windows for additional details.} +% +% +% \subsection{Compiling documents using \pytex} +% +% Compiling a document with \pytex\ involves three steps: running a \LaTeX-compatible \TeX\ engine (binary executable), running |pythontex.py| (preferably via a symlink, wrapper, or batch file, as described above), and finally running the \TeX\ engine again. The first \TeX\ run saves code into an external file where \pytex\ can access it. The second \TeX\ run pulls the \pytex\ output back into the document. +% +% If you plan to use code that contains non-ASCII characters such as Unicode, you should make sure that your document is properly configured: +% \begin{itemize} +% \item Under pdfLaTeX, your documents need |\usepackage[T1]{fontenc}| and |\usepackage[utf8]{inputenc}|, or a similar configuration. +% \item Under LuaLaTeX, your documents need |\usepackage{fontspec}|, or a similar configuration. +% \item Under XeLaTeX, your documents need |\usepackage{fontspec}| as well as |\defaultfontfeatures{Ligatures=TeX}|, or a similar configuration. +% \end{itemize} +% For an example of a \pytex\ document that will correctly compile under all three engines, see the |pythontex_gallery.tex| source. +% +% If you use XeLaTeX, and your non-\LaTeX\ code contains tabs, you \textbf{must} invoke XeLaTeX with the |-8bit| option so that tabs will be written to file as actual tab characters rather than as the character sequence |^^I|.\footnote{See \url{http://tex.stackexchange.com/questions/58732/how-to-output-a-tabulation-into-a-file} for more on tabs with XeTeX.} +% +% |pythontex.py| requires a single command-line argument: the name of the .tex file to process. The filename can be passed with or without an extension; the script really only needs the |\jobname|, so any extension is stripped off.\footnote{Thus, \pytex\ works happily with .tex, .ltx, .dtx, and any other extension.} The filename may include the path to the file; you do not have to be in the same directory as the file to run \pytex. If you are configuring your editor to run \pytex\ automatically via a shortcut, you may want to wrap the filename in double quotes |"| to allow for space characters.\footnote{Using spaces in the names of .tex files is apparently frowned upon. But if you configure things to handle spaces whenever it doesn't take much extra work, then that's one less thing that can go wrong.} For example, under Windows with \TeX\ Live and Python 2.7 we would create the wrapper |pythontex.exe|. Then we could run \pytex\ on a file \meta{file~name}.tex using the command |pythontex.exe "|\meta{file~name}|"|. +% +% |pythontex.py| accepts the following optional command-line arguments. Some of these options duplicate package-level options, so that settings may be configured either within the document or at the command line. In the event that the command-line and package options conflict, the package options always override the command-line options. For variations on these options that are acceptable, run |pythontex.py -h|. +% \begin{itemize} +% \item |--encoding=|\meta{encoding} This sets the file encoding. Any encoding supported by Python's \href{http://docs.python.org/library/codecs.html}{\texttt{codecs}} module may be used. The encoding should match that of the \LaTeX\ document. If an encoding is not specified, \pytex\ uses UTF-8. If support for characters beyond ASCII is required, then additional \LaTeX\ packages are required; see the discussion of \TeX\ engines above. +% \item |--error-exit-code={true,false}| By default, |pythontex.py| returns an exit code of 1 if there were any errors, and an exit code of 0 otherwise. This may be useful when \pytex\ is used in a scripting or command-line context, since the presence of errors may be easily detected. It is also useful with some \TeX\ editors. For example, \href{http://www.tug.org/texworks/}{TeXworks} automatically hides the output of external programs unless there are errors. +% +% In some contexts, returning a nonzero exit code can be redundant. For example, with the \href{http://winshell.de/}{WinShell} editor under Windows with TeX Live, the complete output of \pytex\ is always available in the ``Output'' view, so it is clear if errors have occurred. Having a nonzero exit code causes |runscript.exe| to return an additional, redundant error message in the ``Output'' view. In such situations, it may be desirable to disable the nonzero exit code. +% \item |--runall=[{true,false}]| This causes \textbf{all} code to be executed, regardless of modification or |rerun| settings. It is useful when code has not been modified, but a dependency such as a library or external data has changed. Note that the \pytex\ utilities class also provides a mechanism for automatically re-executing code that depends on external files when those external files are modified. +% +% There is an equivalent |runall| package option. The command-line option |--rerun=always| is essentially equivalent. +% \item |--rerun={never,modified,errors,warnings,always}| This sets the threshold for re-executing code. By default, \pytex\ will rerun code that has been modified or that produced errors on the last run. Sometimes, we may wish to have a more lenient setting (only rerun if modified) or a more stringent setting (rerun even for warnings, or just rerun everything). |never| never executes code; a warning is issued if there is modified code. |modified| only executes code that has been modified (or that has modified dependencies). |errors| executes all modified code as well as all code that produced errors on the last run; this is the default. |warnings| executes all modified code, as well as all code that produced errors or warnings. |always| executes all code always and is essentially equivalent to |--runall|. +% +% There is an equivalent |rerun| package option. +% \item |--hashdependencies=[{true,false}]| This determines whether dependencies (external files highlighted by Pygments, code dependencies specified via |pytex.add_dependencies()|, etc.) are checked for changes via their hashes or modification times. By default, mtime is used, since it is faster. The package option |hashdependencies| is equivalent. +% +% \item |--verbose| This gives more verbose output, including a list of all processes that are launched. +% +% \item |--interpreter| This allows the interpreter for a given language to be specified. The argument should be in the form +% \begin{verbatim} +%--interpreter "<interpreter>:<command>, <interp>:<cmd>, ..." +% \end{verbatim} +% where |<interpreter>| is |python|, |ruby|, etc., and |<command>| is the command for invoking the desired interpreter. The argument to |--interpreter| may also be in the form of a Python dictionary. The argument need not be enclosed in quotation marks if it contains no spaces. +% +% For example, by default Python code is executed with whatever interpreter the |python| command invokes. But Python 3 could be specified using |--interpreter python:python3| (many Linux distributions) or |--interpreter "python:py -3"| (Windows, with Python 3.3 installed so that the |py| wrapper is available). +% \end{itemize} +% +% \pytex\ attempts to check for a wide range of errors and return meaningful error messages. But due to the interaction of \LaTeX\ and Python code, some strange errors are possible. If you cannot make sense of errors when using \pytex, the simplest thing to try is deleting all files created by \pytex, then recompiling. By default, these files are stored in a directory called |pythontex-files-|\meta{jobname}, in the same directory as your .tex document. See Section \ref{sec:troubleshooting} for more details regarding troubleshooting. +% +% +% \section{Usage} +% \label{sec:usage} +% +% \subsection{Package options} +% Package options may be set in the standard manner when the package is loaded: +% \begin{quote} +% |\usepackage|\oarg{options}|{pythontex}| +% \end{quote} +% +% All options are described as follows. The option is listed, followed by its possible values. When a value is not required, \meta{none} is listed as a possible value. In this case, the value to which \meta{none} defaults is also given. Each option lists its default setting, if the option is not invoked when the package is loaded. +% +% Some options have a command-line equivalent. Package options override command-line options. +% +% All options related to printed content are provided in two forms for convenience: one based on the word |print| and one based on |stdout|. +% +% +% \DescribeMacro{usefamily=\meta{basename}/\marg{basename1,~basename2,~...}} +% +% By default, only the |py|, |sympy|, and |pylab| families of commands and environments are defined, to prevent possible package conflicts.\footnote{For example, a \texttt{\string\ruby} command for Ruby code, and the \texttt{\string\ruby} command defined by the Ruby package in the \href{http://www.ctan.org/pkg/cjk}{CJK package}.} This option defines preconfigured families for other available languages. It takes either a single language base name, or a list of comma-separated names enclosed in curly braces. Currently, the Ruby families |rb| and |ruby| may be created. +% +% +% \DescribeMacro{gobble=none/auto default:none} +% +% This option is still under development and may change somewhat in future releases. If that occurs, equivalent functionality will be provided. +% +% This option determines how code indentation is handled. By default, indentation is left as-is; leading whitespace is significant. |auto| will dedent all code by gobbling the largest common leading whitespace, using Python's |textwrap.dedent()|.\footnote{It would be possible to do the dedent on the \LaTeX\ side, as is done manually in the \texttt{fancyvrb} and \texttt{listings} packages with the \texttt{gobble} option and is done automatically in the \texttt{lstautogobble} package. This is not done for stability and security reasons. \texttt{lstautogobble} determines the dedent by extracting the leading whitespace from the first line of code, and then applying this dedent to each subsequent line. This is adequate for \textbf{typesetting} code, since the worst-case scenario is that a subsequent line with less indentation will be typeset with the first few characters missing. Such an approach is not acceptable when the code will be \textbf{executed}, since a few missing characters could in principle cause serious damage. Doing the dedent on the Python side ensures that no characters are discarded, even if that results in an indentation error.} Keep in mind that Python's dedent will not work correctly with mixed tabs and spaces. +% +% The |gobble| option always works correctly with \textbf{executed} code. However, currently the option \textbf{only works with typeset code when Pygments is used}. The option is currently only available at the document level, but finer-grained control is planned in the future. +% +% The |gobble| option is supported by |depythontex|. +% +% +% \DescribeMacro{beta=\meta{none}/true/false default:false \meta{none}=true} +% +% This option provides compatibility with the beta releases from before the full v0.11 release, which introduced some changes in syntax and command names. This option should \textbf{only} be used with old \pytex\ documents that require it. +% +% You are encouraged to update old documents, since this compatility option will only be provided for a few releases. +% +% +% \DescribeMacro{runall=\meta{none}/true/false default:false \meta{none}=true} +% +% This option causes all code to be executed, regardless of whether it has been modified. This option is primarily useful when code depends on external files, and needs to be re-executed when those external files are modified, even though the code itself may not have changed. Note that the \pytex\ utilities class also provides a mechanism for automatically re-executing code that depends on external files when those external files are modified. +% +% A command-line equivalent |--runall| exists for |pythontex.py|. The package option |rerun=always| is essentially equivelent. +% +% +% \DescribeMacro{rerun=never/modified/errors/warnings/always default:errors} +% +% This option sets the threshold for re-executing code. By default, \pytex\ will rerun code that has been modified or that produced errors on the last run. Sometimes, we may wish to have a more lenient setting (only rerun if modified) or a more stringent setting (rerun even for warnings, or always rerun). |never| never executes code; a warning is issued if there is modified code. |modified| only executes code that has been modified. |errors| executes all modified code as well as all code that produced errors on the last run; this is the default. |warnings| executes all modified code, as well as all code that produced errors or warnings. |always| executes all code regardless of its condition. +% +% A command-line equivalent |--rerun| exists for |pythontex.py|. +% +% +% \DescribeMacro{hashdependencies=\meta{none}/true/false default:false \meta{none}=true} +% +% When external code files are highlighted with Pygments, or external dependencies are specified via the \pytex\ utilities class, they are checked for modification via their modification time (Python's |os.path.getmtime()|). Usually, this should be sufficient---and it offers superior performance, which is important if data sets are large enough that hashing takes a noticeable amount of time. However, occasionally hashing may be necessary or desirable, so this option is provided. +% +% A command-line equivalent |--hashdependencies| exists for |pythontex.py|. +% +% +% \DescribeMacro{autoprint=\meta{none}/true/false default:true \meta{none}=true} +% +% \DescribeMacro{autostdout=\meta{none}/true/false default:true \meta{none}=true} +% +% Whenever a |print| command/statement is used, the printed content will automatically be included in the document, unless the code doing the printing is being typeset.\footnote{Note that |autoprint| only works within the body of the document. The |code| command and environment can be used in the preamble, but |autoprint| is disabled there. It is usually a not a good idea to print in the preamble, because nothing can be typeset; the only thing that could be validly printed is \LaTeX\ commands that do not typeset content, such as macro definitions. Thus, it is appropriate that printed content is only brought in while in the preamble if it is explicitly requested via |\string\printpythontex|. This approach is also helpful for writing packages using \pytex, since the author does not have to worry about any \LaTeX\ commands printed by the package either not being included (if |autoprint| is relied upon, but the user turns it off) or being included twice (if |\string\printpythontex| is used and |autoprint| is enabled). Printing should only be used in the preamble with great care.} In that case, the printed content must be included using the |\printpythontex| or |\stdoutpythontex| commands. +% +% Printed content is pulled in directly from the external file in which it is saved, and is interpreted by \LaTeX\ as \LaTeX\ code. If you wish to avoid this, you should print appropriate \LaTeX\ commands with your content to ensure that it is typeset as you desire. Alternatively, you may use |\printpythontex| or |\stdoutpythontex| to bring in printed content in verbatim form, using those commands' optional |verb| and |verbatim| options. +% +% The |autoprint| (|autostdout|) option sets autoprint behavior for the entire document. This may be overridden within the document using the |\setpythontexautoprint| command. +% +% \DescribeMacro{debug} +% +% This option aids in debugging invalid \LaTeX\ code that is brought in from Python. It disables the inclusion of printed content/content written to stdout. Since printed content should almost \textbf{always} be included, a warning is raised when this option is used. +% +% Not including printed content is useful when the printed content contains \LaTeX\ errors, and would cause document compilation to fail. When the document fails to compile, this can prevent modified Python code from being written to the code file, resulting in an inescapable loop unless printed content is disabled or the saved output is deleted. +% +% Note that since commands like |\py| involve printing, they are also disabled. +% +% +% \DescribeMacro{makestderr=\meta{none}/true/false default:false \meta{none}=true} +% +% This option determines whether the stderr produced by scripts is available for input by \pytex, via the |\stderrpythontex| macro. This will not be needed in most situations. It is intended for typeseting incorrect code next to the errors that it produces. This option is not |true| by default, because additional processing is required to synchronize stderr with the document. +% +% \DescribeMacro{stderrfilename=full/session/genericfile/genericscript default:full} +% +% This option governs the file name that appears in |stderr|. Python errors begin with a line of the form +% \begin{quote} +% | File "<file or source>", line <line>| +% \end{quote} +% By default (option |full|), |<file or source>| is the actual name of the script that was executed. The name will be in the form \meta{family~name}|_|\meta{session}|_|\meta{restart}|.|\meta{extension}. For example, an error produced by a |py| command or environment, in the session |mysession|, using the default restart (that is, the default |\restartpythontexsession| treatment), would be reported in |py_mysession_default.py|. The |session| option replaces the full file name with the name of the session, |mysession.py| in this example. The |genericfile| and |genericscript| options replace the file name with |<file>| and |<script>|, respectively. +% +% \DescribeMacro{pyfuture=none/all/default default:default} +% +% Under Python 2, this determines what is automatically imported from |__future__| for all code. It does not apply to |console| content. |none| imports nothing from |__future__|. |all| imports everything available in Python 2.7 (|absolute_import|, |division|, |print_function|, and |unicode_literals|). |default| imports a default set of features that should be compatible with almost all packages. Everything except |unicode_literals| is imported, since |unicode_literals| can occasionally cause conflicts. Note that imports from |__future__| are also allowed within sessions, so long as they are at the very beginning of the session, as they would have to be in a normal script. +% +% This option has no effect under Python 3. +% +% +% \DescribeMacro{pyconfuture=none/all/default default:none} +% +% This is the equivalent of |pyfuture| for Python |console| content. The two options are separate, because in the |console| context it may be desirable to show explicitly all code that is executed. +% +% +% \DescribeMacro{upquote=\meta{none}/true/false default:true \meta{none}=true} +% +% This option determines whether the |upquote| package is loaded. In general, the |upquote| package should be loaded, because it ensures that quotes within verbatim contexts are ``upquotes,'' that is, \expandafter|\textquotesingle| rather than |'|. +% +% Using |upquote| is important beyond mere presentation. It allows code to be copied directly from the compiled PDF and executed without any errors due to quotes |'| being copied as acute accents \texttt{\'}. +% +% \DescribeMacro{fixlr=\meta{none}/true/false default:false \meta{none}=true} +% +% This option removes ``extra'' spacing around |\left| and |\right| in math mode. This spacing is sometimes undesirable, especially when typesetting functions such as the trig functions. See the implementation for details. Similar functionality is provided by the \href{http://www.ctan.org/pkg/mleftright}{\texttt{mleftright}} package +% +% \DescribeMacro{keeptemps=\meta{none}/all/code/none default:none \meta{none}=all} +% +% When \pytex\ runs, it creates a number of temporary files. By default, none of these are kept. The |none| option keeps no temp files, the |code| option keeps only code temp files (these can be useful for debugging), and the |all| option keeps all temp files (code, stdout and stderr for each code file, etc.). Note that this option does not apply to any user-generated content, since \pytex\ knows very little about that; it only applies to files that \pytex\ automatically creates by itself. +% +% \DescribeMacro{prettyprinter=pygments/fancyvrb default:pygments} +% +% This allows the user to determine at the document level whether code is typeset using Pygments or |fancyvrb|. +% +% The package-level option can be overridden for individual command and environment families, using the |\setpythontexprettyprinter| command. Overriding is never automatic and should generally be avoided, since using Pygments to highlight only some content results in an inconsistent style. Keep in mind that Pygment's |text| lexer and/or |bw| style can be used when content needs little or no syntax highlighting. +% +% +% \DescribeMacro{prettyprintinline=\meta{none}/true/false default:true \meta{none}=true} +% +% This determines whether inline content is pretty printed. If it is turned off, inline content is typeset with |fancyvrb|. +% +% +% \DescribeMacro{pygments=\meta{none}/true/false default:true \meta{none}=true} +% +% This allows the user to determine at the document level whether code is typeset using Pygments rather than |fancyvrb|. It is an alias for |prettyprinter=pygments|. +% +% +% \DescribeMacro{pyginline=\meta{none}/true/false default:true \meta{none}=true} +% +% This option governs whether inline code, not just code in environments, is highlighted when Pygments highlighting is in use. When Pygments is in use, it will highlight everything by default. +% +% It is an alias for |prettyprintinline|. +% +% +% \DescribeMacro{pyglexer=\meta{pygments~lexer} default:\meta{none}} +% +% This allows a Pygments lexer to be set at the document level. In general, this option should \textbf{not} be used. It overrides the default lexer for all commands and environments, for both \pytex\ and Pygments content, and this is usually not desirable. It should be useful primarily when all content uses the same lexer, and multiple lexers are compatible with the content. +% +% +% \DescribeMacro{pygopt=\marg{pygments~options} default:\meta{none}} +% +% This allows Pygments options to be set at the document level. The options must be enclosed in curly braces |{}|. Currently, three options may be passed in this manner: |style=|\meta{style~name}, which sets the formatting style; |texcomments|, which allows \LaTeX\ in code comments to be rendered; and |mathescape|, which allows \LaTeX\ math mode (|$...$|) in comments. The |texcomments| and |mathescape| options may be used with an argument (for example, |texcomments=True/False|); if an argument is not supplied, |True| is assumed. Example: |pygopt={style=colorful, texcomments=True, mathescape=False}|. +% +% Pygments options for individual command and environment families may be set with the |\setpythontexpygopt| macro; for Pygments content, there is |\setpygmentspygopt|. These individual settings are always overridden by the package option. +% +% +% \DescribeMacro{fvextfile=\meta{none}/\meta{integer} default:$\infty$ \meta{none}=25} +% +% This option speeds the typesetting of long blocks of code that are created on the Python side. This includes content highlighted using Pygments and the |console| environment. Typesetting speed is increased at the expense of creating additional external files (in the \pytex\ directory). The \meta{integer} determines the number of lines of code at which the system starts using multiple external files, rather than a single external file. See the implementation for the technical details; basically, an external file is used rather than |fancyvrb|'s |SaveVerbatim|, which becomes increasingly inefficient as the length of the saved verbatim content grows. In most situations, this option should not be needed, or should be fine with the default value or similar ``small'' integers. +% +% +% \DescribeMacro{pyconbanner=none/standard/default/pyversion default:none} +% +% This option governs the appearance (or disappearance) of a banner at the beginning of Python console environments. (A banner only appears in the first environment within each session.) The options |none| (no banner), |standard| (standard Python banner), |default| (default banner for Python's |code| module, standard banner plus interactive console class name), and |pyversion| (banner in the form |Python x.y.z|) are accepted. +% +% +% \DescribeMacro{pyconfilename=stdin/console default:stdin} +% +% This governs the form of the filename that appears in error messages in Python console environments. Python errors messages have a form such as the following: +% \begin{verbatim} +% >>> z = 1 + 34 + +% File "<name>", line 1 +% z = 1 + 34 + +% ^ +% SyntaxError: invalid syntax +% \end{verbatim} +% The |stdin| option replaces |<name>| with |<stdin>|, as it appears in a standard Python interactive session. The |console| option uses |<console>| instead, which is the default setting for the Python |code| module used by \pytex\ to create Python console environments. +% +% \DescribeMacro{depythontex=\meta{none}/true/false default:false \meta{none}=true} +% +% This option is used to create a version of the \LaTeX\ document that does not require the \pytex\ package. When invoked, it creates an auxiliary file called |<filename>.depytx|. The script |depythontex.py| uses the original document and this auxiliary file to create a new document in which all \pytex\ commands and environments have been replaced by typeset code and code output. For additional information on |depythontex|, see Section~\ref{sec:depythontex}. +% +% +% \subsection{Commands and environments} +% +% \pytex\ provides four types of commands for use with inline code and three environments for use with multiple lines of code, plus special commands and environments for |console| content. All commands and environments are named using a base name and a command- or environment-specific suffix. A complete set of commands and environments with the same base name constitutes a \textbf{command and environment family}. In what follows, the different commands and environments are described using the |py| base name (the |py| family) as an example. +% +% Most commands and environments cannot be used in the preamble, because they typeset material and that is not possible in the preamble. The one exception is the |code| command and environment. These can be used to enter code, but need not typeset anything. This allows you to collect your \pytex\ code in the preamble, if you wish, or even use \pytex\ in package writing. Note that the package option |autoprint| is never active in the preamble, so even if a |code| command or environment did print in the preamble, printed content would never be inputted unless |\printpythontex| or |\stdoutpythontex| were used. +% +% All commands and environments take a session name as an optional argument. The session name determines the session in which the code is executed. This allows code to be executed in multiple independent sessions, increasing speed (sessions run in parallel) and preventing naming conflicts. If a session is not specified, then the |default| session is used. Session names should use the characters a-z, A-Z, 0-9, the hyphen, and the underscore. All characters used \textbf{must} be valid in file names, since session names are used to create temporary files. The colon is also allowed, but it is replaced with a hyphen internally, so the sessions |code:1| and |code-1| are identical. +% +% In addition, all environments take |fancyvrb| settings as a second, optional argument. See the \href{http://www.ctan.org/tex-archive/macros/latex/contrib/fancyvrb}{|fancyvrb| documentation} for an explanation of accepted settings. This second optional argument \textbf{must} be preceeded by the first optional argument (session name). If a named session is not desired, the optional argument can be left empty (|default| session), but the square brackets |[]| must be present so that the second optional argument may be correctly identified: +% \begin{quote} +% |\begin{|\meta{environment}|}[]|\oarg{fancyvrb~settings} +% \end{quote} +% +% +% \subsubsection{Inline commands} +% \pytxtodo{Fix spacing around |\DescribeMacro|!} +% Inline commands are suitable for single lines of code that need to be executed within the body of a paragraph or within a larger body of text. The commands use arbitrary code delimiters (like |\verb| does), which allows the code to contain arbitrary characters. Note that this is only guaranteed to work properly when the inline commands are \textbf{not} inside other macros. If an inline command is used within another macro, the code will be read by the external macro before \pytex\ can read the special code characters (that is, \LaTeX\ will tokenize the code). The inline commands can work properly within other macros, but it is best to stick with curly braces for delimiters in this case and you may have trouble with the hash |#| and percent |%| characters. +% +% \DescribeMacro{\py\oarg{session}\meta{opening~delim}\meta{code}\meta{closing~delim}} +% +% This command is used for including variable values or other content that can be converted to a string. It is an alternative to including content via the |print| statement/function within other commands/environments. +% +% The |\py| command sends \meta{code} to Python, and Python returns a string representation of \meta{code}.\pytxtodo{Link to details about Python built-ins} \meta{opening~delim} and \meta{closing~delim} must be either a pair of identical, non-space characters, or a pair of curly braces. If curly braces are used as delimiters, then curly braces may only be used within \meta{code} if they are paired. Thus, |\py{1+1}| sends the code |1+1| to Python, Python evaluates the string representation of this code, and the result is returned to \LaTeX\ and included as |2|. The commands |\py#1+1#| and |\py@1+1@| would have the same effect. The command can also be used to access variable values. For example, if the code |a=1| had been executed previously, then |\py{a}| simply brings the string represantation of |a| back into the document as |1|. +% +% Assignment is \textbf{not} allowed using |\py|. For example, |\py{a=1}| is \textbf{not} valid. This is because assignment cannot be converted to a string.\footnote{It would be simple to allow any code within |\textbackslash py|, including assignment, by using a |try/except| statement. In this way, the functionality of |\textbackslash py| and |\textbackslash pyc| could be merged. While that would be simpler to use, it also has serious drawbacks. If |\textbackslash py| is not exclusively used to typeset string representations of \meta{code}, then it is no longer possible on the \LaTeX\ side to determine whether a command should return a string. Thus, it is harder to determine, from within a \TeX\ editor, whether |pythontex.py| needs to be run; warnings for missing Python content could not be issued, because the system wouldn't know (on the \LaTeX\ side) whether content was indeed missing.} +% +% The text returned by Python must be valid \LaTeX\ code. Verbatim and other special content is allowed under the pdfTeX and XeTeX engines (a known bug prevents it from working with LuaTeX). The primary reasons for using |\py| rather than |print| are (1) |\py| is more compact and (2) |print| requires an external file to be created for every command or environment in which it is used, while |\py| and equivalents for other families share a single external file. Thus, use of |\py| minimizes the creation of external files, which is a key design goal for \pytex.\footnote{For |\textbackslash py|, the text returned by Python is stored in macros and thus must be valid \LaTeX\ code, because \LaTeX\ interprets the returned content. The use of macros for storing returned content means that an external file need not be created for each use of |\textbackslash py|. Rather, all macros created by |\textbackslash py| and equivalent commands from other families are stored in a single file that is inputted. Note that even though the content is stored in macros, verbatim content is allowed, through the use of special macro definitions combined with \texttt{\string\scantokens}.} The main reason for using |print| rather than |\py| is if you need to include a very large amount of material; |print|'s use of external files won't use up \TeX's memory, and may give noticeably better performance once the material is sufficiently long. +% +% \DescribeMacro{\pyc\oarg{session}\meta{opening~delim}\meta{code}\meta{closing~delim}} +% +% This command is used for executing but not typesetting \meta{code}. The suffix |c| is an abbreviation of |code|. If the |print| statement/function is used within \meta{code}, printed content will be included automatically so long as the package |autoprint| option is set to true (the default setting). +% +% \DescribeMacro{\pyv\oarg{session}\meta{opening~delim}\meta{code}\meta{closing~delim}} +% +% This command is used for typesetting but not executing \meta{code}. The suffix |v| is an abbreviation for |verb|. +% +% \DescribeMacro{\pyb\oarg{session}\meta{opening~delim}\meta{code}\meta{closing~delim}} +% +% This command both executes and typesets \meta{code}. Since it is unlikely that the user would wish to typeset code and then \textbf{immediately} include any output of the code, printed content is \textbf{not} automatically included, even when the package |autoprint| option is set to true. Rather, any printed content is included at a user-designated location via the |\printpythontex| or |\stdoutpythontex| macros. +% +% +% \subsubsection{Environments} +% +% \DescribeEnv{pycode~\oarg{session}\oarg{fancyvrb~settings}} +% +% This environment encloses code that is executed but not typeset. The second optional argument \meta{fancyvrb~settings} is irrelevant since nothing is typeset, but it is accepted to maintain parallelism with the |verbatim| and |block| environments. If the |print| statement/function is used within the environment, printed content will be included automatically so long as the package |autoprint| option is set to true (the default setting). +% +% +% \DescribeEnv{pyverbatim~\oarg{session}\oarg{fancyvrb~settings}} +% +% This environment encloses code that is typeset but not executed. +% +% +% \DescribeEnv{pyblock~\oarg{session}\oarg{fancyvrb~settings}} +% +% This environment encloses code that is both executed and typeset. Since it is unlikely that the user would wish to typeset code and then \textbf{immediately} print any output of the code, printed content is \textbf{not} automatically included, even when the package |autoprint| option is set to true. Rather, any printed content is included at a user-designated location via the |\printpythontex| or |\stdoutpythontex| macros. +% +% +% \subsubsection{Console command and environment families} +% +% So far, we have considered the |py| command and environment family. \pytex\ also provides families for |console| content. These emulate the behavior of a Python interactive console. In what follows, the |pycon| family is described +% +% The |pycon| family includes a |\pyconv| and |pyconverbatim| that typeset a console session pasted from an interpreter. It also includes a |\pyconc| and |pyconcode| that execute code but typeset nothing. These should be used with care, since it may often be advisable to show all executed code when working with an interactive console. +% +% The |pycon| family also includes a special environment and command. +% +% \DescribeEnv{pyconsole~\oarg{session}\oarg{fancyvrb~settings}} +% +% This environment treats its contents as a series of commands passed to an interactive Python console. Python's \href{http://docs.python.org/3/library/code.html}{\texttt{code}} module is used to intersperse the commands with their output, to emulate an interactive Python interpreter. +% +% When a multi-line command is entered (for example, a function definition), a blank line after the last line of the command may be necessary. +% +% For example, +% \begin{verbatim} +%a = 1 +%b = 2 +%a + b +% \end{verbatim} +% produces +% \begin{verbatim} +%>>> a = 1 +%>>> b = 2 +%>>> a + b +%3 +% \end{verbatim} +% \DescribeMacro{\pycon\oarg{session}\meta{opening~delim}\meta{code}\meta{closing~delim}} +% +% This command executes \meta{code} using the emulated interpreter, and brings the output back into the document, \textbf{discarding the input}. The output is typeset verbatim (since it will not in general be valid \LaTeX), with the same font used for the |pyconsole| environment. +% +% For example, |\pycon{a + b}| would create |3|. +% +% This command is primarily for use in referencing console variable values. +% +% Notice that there is \textbf{not} a command or environment for |console| content that parallels the |block| command and environment. That is, there is not a command or environment that both typesets and executes code in the console, but does not show the output. This is intentional. In most cases, if you are going to use the console, you should use it consistently, showing input and output together. +% +% \subsubsection{Default families} +% +% By default, three command and environment families are defined, with three corresponding console families. +% \begin{itemize} +% \item Python +% \begin{itemize} +% \item Base name |py|: |\py|, |\pyc|, |\pyv|, |\pyb|, |pycode|, |pyverbatim|, |pyblock|. +% \item Base name |pycon|: |\pycon|, |\pyconc|, |\pyconv|, |pyconsole|, |pyconcode|, |pyconverbatim|. +% \item Imports: None. +% \end{itemize} +% \item Python + pylab (matplotlib module) +% \begin{itemize} +% \item Base name |pylab|: |\pylab|, |\pylabc|, |\pylabv|, |\pylabb|, |pylabcode|, |pylabverbatim|, |pylabblock|. +% \item Base name |pylabcon|: |\pylabcon|, |\pylabconc|, |\pylabconv|, |pylabconsole|, |pylabconcode|, |pylabconverbatim|. +% \item Imports: matplotlib's |pylab| module, which provides access to much of matplotlib and NumPy within a single namespace. pylab content is brought in via |from pylab import *|. +% \item Additional notes: matplotlib added a \href{http://matplotlib.org/users/pgf.html}{\texttt{pgf} backend} in version 1.2. You will probably want to use this for creating most plots. However, this is not currently configured automatically because many users will want to customize font, \TeX\ engine, and other settings. Using \TeX\ to create plots also introduces a performance penalty. +% \end{itemize} +% \item Python + SymPy +% \begin{itemize} +% \item Base name |sympy|: |\sympy|, |\sympyc|, |\sympyv|, |\sympyb|, |sympycode|, |sympyverbatim|, |sympyblock|, |sympyconsole|. +% \item Base name |sympycon|: |\sympycon|, |\sympyconc|, |\sympyconv|, |sympyconsole|, |sympyconcode|, |sympyconverbatim|. +% \item Imports: SymPy via |from sympy import *|. +% \item Additional notes: By default, content brought in via |\sympy| is formatted using a context-sensitive interface to SymPy's |LatexPrinter| class, described below. +% \end{itemize} +% \end{itemize} +% +% Under Python 2.7, all non-|console| families import |absolute_import|, |division|, and |print_function| from |__future__| by default. This may be changed using the package option |pyfuture|. There is an equivalent |pyconfuture| for |console| families. Keep in mind that importing |unicode_literals| from |__future__| may break compatibility with some packages; this is why it is not imported by default. Imports from |__future__| are also possible without using the |pyfuture| option. You may use the |\pythontexcustomc| command or |pythontexcustomcode| environment (described below), or simply enter the import commands immediately at the beginning of a session. +% +% +% \subsubsection{Custom code} +% +% You may wish to customize the behavior of one or more families within a document by adding custom code to the beginning and end of each session. The custom code command and environment make this possible. While the custom code command and environment work with |console| content, most of the discussion below is geared toward the non-|console| case. +% +% If you wish to share these customizations among several documents, you can create your own document class or package containing custom code commands and environments. +% +% While custom code can be added anywhere in a document, it is probably best for organizational reasons to add it in the preamble or near the beginning of the document. +% +% Note that custom code is executed, but never typeset. Only code that is actually entered within a |block| (or |verbatim|) command or environment is ever typeset. This means that you should be careful about how you use custom code. For example, if you are documenting code, you probably want to show absolutely all code that is executed, and in that case using custom code might not be appropriate. If you are using \pytex\ to create figures or automate text, are using many sessions, and require many imports, then custom code could save some typing by centralizing the imports. +% +% Any errors or warnings due to custom code will be correctly synchronized with the document, just like normal errors and warnings. Any errors or warnings will be specifically identified as originating in custom code. +% +% Custom code is not allowed to print or write to stdout. It would be pointless for custom code at the beginning of a session to print, because all printed content would be identical since custom code at the beginning comes before any regular code that might make the output session-specific. In addition, it is not obvious where printed content from custom code would be included, especially for custom code at the end of a session. Furthermore, custom code may be in the preamble, where nothing can be typeset. +% +% If custom code does attempt to print, a warning is raised and the printed content is included in the \pytex\ run summary. This gives you access to the printed content, while not including it in the document. This can be useful in cases where you cannot control whether content prints (for example, if a library automatically prints debugging information). +% +% \DescribeMacro{\pythontexcustomc\oarg{position}\marg{family}\marg{code}} +% +% \pytxtodo{Look into options for setting custom code for an entire language at a time. It would be possible to have the family be optional, but while that would be good as long as only Python is supported, it would quickly start causing problems as soon as any additional languages are added. Basically, there needs to be a way to set custom code for all sessions sharing a language. Perhaps some kind of keyword system, like language:python being used for family.} +% This macro allows custom code to be added to all sessions within a command and environment family. \meta{position} should be either |begin| or |end|; it determines whether the custom code is executed at the beginning or end of each session. By default, custom code is executed at the beginning. \meta{code} should be a \textbf{single line} of code. For example, |\pythontexcustomc{py}{a=1; b=2}| would create the variables |a| and |b| within all sessions of the |py| family, by adding that line of code at the beginning of each session. +% +% If you need to add more than a single line of custom code, you could use the command multiple times, but it will be more efficient to use the |pythontexcustomcode| environment. +% +% \meta{code} may contain imports from |__future__|. These must be the first elements in any custom code command or environment, since |__future__| imports are only possible at the very beginning of a Python script and only the very beginning of custom code is checked for them. If imports from |__future__| are present at the beginning of both custom code and the user's code, all imports will work correctly; the presence of the imports in custom code, before user code, does not turn off checking for |__future__| imports at the very beginning of user code. However, it is probably best to keep all |__future__| imports in a single location. +% +% \DescribeEnv{pythontexcustomcode\oarg{position}\marg{family}} +% +% This is the environment equivalent of |\pythontexcustomc|. It is used for adding multi-line custom code to a command and environment family. In general, the environment should be preferrred to the command unless only a very small amount of custom code is needed. The environment has the same properties as the command, including the ability to contain imports from |__future__|. +% +% +% \subsubsection{\pytex\ utilities class} +% \label{sec:usage:commands:utilities-class} +% +% All non-|console| families import |pythontex_utils.py|, and create an instance of the \pytex\ utilities class called |pytex|. This provides various utilities for interfacing with \LaTeX\ and \pytex. +% +% The utilities class provides an interface for determining how Python objects are converted into strings in commands such as |\py|. The |pytex.set_formatter(|\meta{formatter}|)| method is used to set the conversion. Two formatters are provided: +% \begin{itemize} +% \item |'str'| converts Python objects to a string, using the |str()| function under Python 3 and the |unicode()| function under Python 2. (The use of |unicode()| under Python 2 should not cause problems, even if you have not imported |unicode_literals| and are not using unicode strings. All encoding issues should be taken care of automatically by the utilities class.) +% \item |'sympy_latex'| uses SymPy's |LatexPrinter| class to return context-sensitive \LaTeX\ representations of SymPy objects. Separate |LatexPrinter| settings may be created for the following contexts: |'display'| (displaystyle math), |'text'| (textstyle math), |'script'| (superscripts and subscripts), and |'scriptscript'| (superscripts and subscripts, of superscripts and subscripts). Settings are created via |pytex.set_sympy_latex(|\meta{context}|,|\meta{settings}|)|. For example, |pytex.set_sympy_latex('display', mul_symbol='times')| sets multiplication to use a multiplication symbol $\times$, but only when math is in displaystyle.\footnote{Internally, the |'sympy\_latex'| formatter uses the |\textbackslash mathchoice| macro to return multiple representations of a SymPy object, if needed by the current settings. Then |\textbackslash mathchoice| typesets the correct representation, based on context.} See the \href{http://docs.sympy.org/dev/modules/printing.html}{SymPy documentation} for a list of possible settings for the |LatexPrinter| class. +% +% By default, |'sympy_latex'| only treats matrices differently based on context. Matrices in displaystyle are typeset using |pmatrix|, while those in all other styles are typeset via |smallmatrix| with parentheses. +% +% The context-sensitive interface to SymPy's |LatexPrinter| is always available via |pytex.sympy_latex()|. +% \end{itemize} +% +% The \pytex\ utilities formatter may be set to a custom function that returns strings, simply by reassigning the |pytex.formatter()| method. For example, define a formatter function |my_func()|, and then |pytex.formatter=my_func|. +% +% The utilities class also provides methods for tracking dependencies and created files. +% \begin{itemize} +% \item |pytex.add_dependencies(|\meta{dependencies}|)| This adds \meta{dependencies} to a list. If any dependencies in the list change, code is re-executed, even if the code itself has not changed (unless |rerun=never|). Modified dependencies are determined via either modification time (default) or hash; see the package option |hashdependencies| for details. This method is useful for tracking changes in external data and similar files. +% +% \meta{dependencies} should be one or more strings, separated by commas, that are the file names of dependencies. Dependencies should be given with relative paths from the current working directory, with absolute paths, or with paths based on the user's home directory (that is, starting with a tilde |~|). Paths can use a forward slash ``|/|'' even under Windows. Remember that by default, the working directory is the |pythontex-files-|\meta{jobname} directory where all \pytex\ temporary files are stored. This can be adjusted with |\setpythontexworkingdir|. +% +% It is possible that a dependency of one session might be modified by another session while \pytex\ runs. The first session might not be executed during the \pytex\ run because its dependency was unmodified at the beginning. A more serious case occurs when the first session does run, but we don't know whether it accessed the dependency before or after the dependency was updated (remember, sessions run in parallel). \pytex\ keeps track of the time at which it started. Any sessions with dependencies that were modified after that time are set to re-execute on the next run. A warning is also issued to indicated that this is the case. +% +% \item |pytex.add_created(|\meta{created~files}|)| This adds \meta{created~files} to a list of files created by the current session. Any time the code for the current session is executed, \textbf{all of these files will be deleted}. Since this method deletes files, it should be used with care. It is intended for automating cleanup when code is modified. For example, if a figure's name is changed, the old figure would be deleted if its name had been added to the list. By default, \pytex\ can only clean up the temporary files it creates; it knows nothing about user-created files. This method allows user-created files to be specified, and thus added to \pytex's automatic cleanup. +% +% \meta{created~files} should be one or more strings, separated by commas, that are the file names of created files. Paths should be the same as for |pytex.add_dependencies()|: relative to the working directory, absolute, or based on the user's home directory. Again, paths can use a forward slash ``|/|'' even under Windows. +% +% Depending on how you use \pytex, this method may not be very beneficial. If all of the output is contained in the default output directory, or a similar directory of your choosing, then manual cleanup may be simple enough that this method is not needed. +% \end{itemize} +% +% These two methods for tracking files may be used manually. However, that is prone to errors, since you will have to modify both a \pytex\ utilities command and an open or save command every time you change a file name or add or remove a dependency or created file. It may be better to redefine your open and save commands, or define new ones, so that a single command opens (or saves) and adds a dependency (or adds a created file). For example, the following would create a version of the standard |open()| that automatically tracks dependencies and created files. +% \begin{verbatim} +%def track_open(file, mode='r', *args, **kwargs): +% if mode in ('r', 'rb'): +% pytex.add_dependencies(file) +% elif mode in ('w', 'wb'): +% pytex.add_created(file) +% return open(file, mode, *args, **kwargs) +%pytex.open = track_open +% \end{verbatim} +% Notice that this approach does not deal with files opened for appending or updating; such cases require more complex logic. +% +% The utilities class provides a pair of methods, |before()| and |after()|, that are called immediately before and after each chunk of user code. These may be redefined to customize the output of user code. For example, \LaTeX\ commands could be printed at the beginning and end of each command or environment, wrapping any content printed by the user. Or any matplotlib figures that were created in the chunk of code could be detected and saved, and \LaTeX\ commands to include them in the document could be printed. Or stdout could be redirected to a StringIO stream in |before()|, then processed in |after()| before being sent to the original stdout. +% +% You may redefine |before()| and |after()| at the class level. For example, +% \begin{verbatim} +%def open(self): +% <body> +%PythontexUtils.open = open +% \end{verbatim} +% Or you may redefine these methods as instance attributes that happen to be functions (rather than bound methods). Notice that in this case |self| is not allowed. +% \begin{verbatim} +%def open(): +% <body> +%pytex.open = open +% \end{verbatim} +% Finally, you may redefine these methods as bound methods for the |pytex| instance. +% \begin{verbatim} +%def open(self): +% <body> +%import types +%pytex.open = types.MethodType(open, pytex) +% \end{verbatim} +% The first and third approaches are necessary if you want to be able to use |self| (for example, to access instance attributes). Notice that |before()| and |after()| take no arguments (except |self| where applicable). +% +% An example of using the |after()| method to automatically save and include all matplotlib figures created in a command or environment is shown below. This example is designed for the |pylab| family of commands, or when |from pylab import *| is used. If |pyplot| is imported as |plt| instead, then |plt.get_fignums()|, |plt.figure()|, |plt.savefig()|, |plt.close()|, etc., would be needed. +% \begin{verbatim} +%# Basename for figures that will be created +%pytex.basename = '_'.join([pytex.input_type, pytex.input_session, +% pytex.input_restart]) +% +%# Need to keep track of total number of figures in each session +%pytex.fignum = 0 +% +%# The figure could be included in more sophisticated ways +%# For example, a ``figure`` environment could be used +%def after(): +% for num in get_fignums(): +% fname = pytex.basename + '_fig' + str(pytex.fignum) + '.pdf' +% pytex.fignum += 1 +% figure(num) +% savefig(fname) +% pytex.add_created(fname) +% close(num) +% print(r'\includegraphics{' + fname + '}') +% +%# In this case, I'm taking the easy approach to redefine ``open()`` +%pytex.after = after +% \end{verbatim} +% +% +% \subsubsection{Formatting of typeset code} +% +% \DescribeMacro{\setpythontexfv\oarg{family}\marg{fancyvrb~settings}} +% +% This command sets the |fancyvrb| settings for all command and environment families. Alternatively, if an optional argument \meta{family} is supplied, the settings only apply to the family with that base name. The general command will override family-specific settings. +% +% Each time the command is used, it completely overwrites the previous settings. If you only need to change the settings for a few pieces of code, you should use the second optional argument in |block| and |verb| environments. +% +% Note that |\setpythontexfv| and |\setpygmentsfv| are equivalent when they are used without an optional argument; in that case, either may be used to determine the document-wide |fancyvrb| settings, because both use the same underlying macro. +% +% +% \DescribeMacro{\setpythontexprettyprinter\oarg{family}\marg{printer}} +% +% This should generally not be needed. It sets the pretty printing used by the document, or by \meta{family} if given. Valid options for \meta{printer} are |fancyvrb| and |pygments|. The option |auto| may be given for \meta{family}, in which case the formatter is inherited from the document-level settings. Using either of the other two options will force \meta{family} to use that printer, regardless of the document-level settings. By default, families use |auto|. +% +% Remember that Pygments has a |text| lexer and a |bw| style. These are an alternative to setting the formatter to |fancyvrb|. +% +% +% \DescribeMacro{\setpythontexpyglexer\oarg{family}\marg{pygments~lexer}} +% +% This allows the Pygments lexer to be set for \meta{family}. \meta{pygments~lexer} should use a form of the lexer name that does not involve any special characters. For example, you would want to use the lexer name |csharp| rather than |C#|. This will be a consideration primarily when using the Pygments commands and environments to typeset code of an arbitrary language. +% +% If a \meta{family} is not specified, the lexer is set for the entire document. +% +% \DescribeMacro{\setpythontexpygopt\oarg{family}\marg{pygments~options}} +% +% This allows the Pygments options for \meta{family} to be redefined. Note that any previous options are overwritten. The same Pygments options may be passed here as are available via the package |pygopt| option. Note that for each available option, individual family settings will be overridden by the package-level |pygopt| settings, if any are given. +% +% If a \meta{family} is not specified, the options are set for the entire document. +% +% +% \subsubsection{Access to printed content (stdout) and error messages (stderr)} +% +% The macros that allow access to printed content and any additional content written to stdout are provided in two identical forms: one based off of the word |print| and one based off of |stdout|. Macro choice depends on user preference. The |stdout| form provides parallelism with the macros that provide accesss to |stderr|. +% +% \DescribeMacro{\printpythontex\oarg{mode}\oarg{options}} +% +% \DescribeMacro{\stdoutpythontex\oarg{mode}\oarg{options}} +% +% Unless the package option |autoprint| is true, printed content from |code| commands and environments will not be automatically included. Even when the |autoprint| option is turned on, |block| commands and environments do not automatically include printed content, since we will generally not want printed content immediately after typeset code. This macro brings in any printed content from the \textbf{last} command or environment. It is reset after each command/environment, so its scope for accessing particular printed content is very limited. It will return an error if no printed content exists. +% +% \meta{mode} determines how printed content is handled. It may be |raw| (interpreted as \LaTeX), |verb| (inline verbatim), or |verbatim|; |raw| is the default. Verbatim content is brought in via |fancyvrb|. \meta{options} consists of |fancyvrb| settings. +% +% +% \DescribeMacro{\saveprintpythontex\marg{name}} +% +% \DescribeMacro{\savestdoutpythontex\marg{name}} +% +% \DescribeMacro{\useprintpythontex\oarg{verbatim~options}\oarg{fancyvrb~options}\marg{name}} +% +% \DescribeMacro{\usestdoutpythontex\oarg{verbatim~options}\oarg{fancyvrb~options}\marg{name}} +% +% We may wish to be able to access the printed content from a command or environment at any point after the code that prints it, not just before any additional commands or environments are used. In that case, we may save access to the content under \meta{name}, and access it later via |\useprintpythontex|\marg{name}. \meta{mode} must be |raw|, |verb|, or |verbatim|. If content is brought in verbatim, then \meta{fancyvrb~options} are applied. +% +% \DescribeMacro{\stderrpythontex\oarg{mode}\oarg{fancyvrb~options}} +% +% This brings in the stderr produced by the last command or environment. It is intended for typesetting incorrect code next to the errors that it produces. By default, stderr is brought in verbatim. \meta{mode} may be set to |raw|, |verb|, or |verbatim|. In general, bringing in stderr |raw| should be avoided, since stderr will typically include special characters that will make \TeX\ unhappy. +% +% The line number given in the |stderr| message will correctly align with the line numbering of the typeset code. Note that this only applies to |code| and |block| environments. Inline commands do not have line numbers, and as a result, they \textbf{do not} produce stderr content. \pytxtodo{Maybe allow stderr for inline, with line number set to 1?} +% +% By default, the file name given in the message will be in the form +% \begin{quote} +% \meta{family~name}|_|\meta{session}|_|\meta{group}|.|\meta{extension} +% \end{quote} +% For example, an error produced by a |pycode| environment, in the session |mysession|, using the default group (that is, the default |\restartpythontexsession| treatment), would be reported in |py_mysession_default.py|. The package option |stderrfilename| may be used to change the reported name to the following forms: |mysession.py|, |<file>|, |<script>|. +% +% +% \DescribeMacro{\savestderrpythontex\marg{name}} +% +% \DescribeMacro{\usestderrpythontex\oarg{mode}\oarg{fancyvrb~options}\marg{name}} +% +% +% Content written to |stderr| may be saved and accessed anywhere later in the document, just as |stdout| content may be. These commands should be used with care. Using Python-generated content at multiple locations within a document may often be appropriate. But an error message will usually be most meaningful in its context, next to the code that produced it. +% +% \DescribeMacro{\setpythontexautoprint\marg{boolean}} +% +% \DescribeMacro{\setpythontexautostdout\marg{boolean}} +% +% This allows autoprint behavior to be modified at various points within the document. The package-level |autoprint| option is also available for setting autoprint at the document level, but it is overridden by |\setpythontexautoprint|. \meta{boolean} should be |true| or |false|. +% +% +% \subsection{Pygments commands and environments} +% +% Although \pytex's goal is primarily the execution and typesetting of Python code from within \LaTeX, it also provides access to syntax highlighting for any language supported by Pygments. +% +% \DescribeMacro{\pygment\marg{lexer}\meta{opening~delim}\meta{code}\meta{closing~delim}} +% +% This command typesets \meta{code} in a suitable form for inline use within a paragraph, using the specified Pygments \meta{lexer}. Internally, it uses the same macros as the \pytex\ inline commands. \meta{opening~delim} and \meta{closing~delim} may be a pair of any characters except for the space character, or a matched set of curly braces |{}|. +% +% As with the inline commands for code typesetting and execution, there is not an optional argument for |fancyvrb| settings, since almost all of them are not relevant for inline usage, and the few that might be should probably be used document-wide if at all. +% +% +% \DescribeEnv{pygments~\oarg{fancyvrb~settings}\marg{lexer}} +% +% This environment typesets its contents using the specified Pygments \meta{lexer} and applying the \meta{fancyvrb~settings}. +% +% +% \DescribeMacro{\inputpygments\oarg{fancyvrb~settings}\marg{lexer}\marg{external~file}} +% +% This command brings in the contents of \meta{external~file}, highlights it using \meta{lexer}, and typesets it using \meta{fancyvrb~settings}. +% +% +% \DescribeMacro{\setpygmentsfv\oarg{lexer}\marg{fancyvrb~settings}} +% +% This command sets the \meta{fancyvrb~settings} for \meta{lexer}. If no \meta{lexer} is supplied, then it sets document-wide \meta{fancyvrb~settings}. In that case, it is equivalent to |\setpythontexfv|\marg{fancyvrb~settings}. +% +% +% \DescribeMacro{\setpygmentspygopt\oarg{lexer}\marg{pygments~options}} +% +% This sets \meta{lexer} to use \meta{pygments~options}. If there is any overlap between \meta{pygments~options} and the package-level |pygopt|, the package-level options override the lexer-specific options. +% +% If \meta{lexer} is not given, options are set for the entire document. +% +% +% \DescribeMacro{\setpygmentsprettyprinter\marg{printer}} +% +% This usually should not be needed. It allows the pretty printer for the document to be set; it is equivalent to using |\setpythontexprettyprinter| without an optional argument. Valid options for \meta{printer} are |fancyvrb| and |pygments|. +% +% Remember that Pygments has a |text| lexer and a |bw| style. These are an alternative to setting the formatter to use |fancyvrb|. +% +% +% \subsection{General code typesetting} +% +% \subsubsection{Listings float} +% +% \DescribeEnv{listing} +% +% \pytex\ will create a float environment |listing| for code listings, unless an environment with that name already exists. The |listing| environment is created using the |newfloat| package. Customization is possible through |newfloat|'s |\SetupFloatingEnvironment| command. +% +% \DescribeMacro{\setpythontexlistingenv\marg{alternate~listing~environment~name}} +% +% In the event that an environment named |listing| already exists for some other purpose, \pytex\ will not override it. Instead, you may set an alternate name for \pytex's |listing| environment, via |\setpythontexlistingenv|. +% +% +% \subsubsection{Background colors} +% +% \pytex\ uses |fancyvrb| internally to typeset all code. Even code that is highlighted with Pygments is typeset afterwards with |fancyvrb|. Using |fancyvrb|, it is possible to set background colors for individual lines of code, but not for entire blocks of code, using |\FancyVerbFormatLine| (you may also wish to consider the |formatcom| option). For example, the following command puts a green background behind all the characters in each line of code: +% \begin{verbatim} +% \renewcommand{\FancyVerbFormatLine}[1]{\colorbox{green}{#1}} +% \end{verbatim} +% +% If you need a completely solid colored background for an environment, or a highly customizable background, you should consider the |mdframed| package. Wrapping \pytex\ environments with |mdframed| frames works quite well. You can even automatically add a particular style of frame to all instances of an environment using the command +% \begin{quote} +% |\surroundwithmdframed|\oarg{frame~options}\marg{environment} +% \end{quote} +% Or you could consider using |etoolbox| to do the same thing with |mdframed| or another framing package of your choice, via |etoolbox|'s |\BeforeBeginEnvironment| and |\AfterEndEnvironment| macros. +% +% +% \subsubsection{Referencing code by line number} +% +% It is possible to reference individual lines of code, by line number. If code is typeset using pure |fancyvrb|, then \LaTeX\ labels can be included within comments. The labels will only operate correctly (that is, be treated as \LaTeX\ rather than verbatim content) if |fancyvrb|'s |commandchars| option is used. For example, |commandchars=\\\{\}| makes the backslash and the curly braces function normally \textbf{within} |fancyvrb| environments, allowing \LaTeX\ macros to work, including label definitions. Once a label is defined within a code comment, then referencing it will return the code line number. +% +% The disadvantage of the pure |fancyvrb| approach is that by making the backslash and curly braces command characters, we can produce conflicts if the code we are typesetting contains these characters for non-\LaTeX\ purposes. In such a case, it might be possible to make alternate characters command characters, but it would probably be better to use Pygments. +% +% If code is typeset using Pygments (which also ties into |fancyvrb|), then this problem is avoided. The Pygments option |texcomments=true| has Pygments look for \LaTeX\ code only within comments. Possible command character conflicts with the language being typeset are thus eliminated. +% +% Note that when labels are created within comments, the labes themselves will be invisible within the final document but the comment character(s) and any other text within comments will still be visible. For example, the following +% \begin{quote} +% |abc = 123 # An important line of code!\label{lst:important}| +% \end{quote} +% would appear as +% \begin{quote} +% |abc = 123 # An important line of code!| +% \end{quote} +% If a comment only contains the |\label| command, then only the comment character |#| would actually be visible in the typeset code. If you are typesetting code for instructional purposes, this may be less than ideal. Unfortunately, Pygments currently does not allow escaping to \LaTeX\ outside of comments (though this feature has been requested). At the same time, by only allowing references within comments, Pygments does force us to create code that would actually run. And in many cases, if a line is important enough to label, it is also important enough for a brief comment. +% +% \subsubsection{Beamer compatibility} +% +% \pytex\ is compatible with \href{http://www.ctan.org/pkg/beamer}{Beamer}. Since \pytex\ typesets code as verbatim content, Beamer's |fragile| option must be used for any frame that contains typeset code. Beamer's |fragile| option involves saving frame contents to an external file and bringing it back in. This use of an external file breaks \pytex's error line number synchronization, since the error line numbers will correspond to the temporary external file rather than to the actual document. +% +% If you need to typeset code with Beamer, but don't need to use overlays on the slides containing code, you should use the |fragile=singleslide| option. This allows verbatim content to be typeset without using an external file, so \pytex's error line syncronization will work correctly. +% +% +% \subsection{Advanced \pytex\ usage} +% +% \DescribeMacro{\restartpythontexsession\marg{counter~value(s)}} +% +% This macro determines when or if sessions are restarted (or ``subdivided''). Whenever \meta{counter~value(s)} change, the session will be restarted. +% +% By default, each session corresponds to a single code file that is executed. But sometimes it might be convenient if the code from each chapter or section or subsection were to run within its own file, as its own session. For example, we might want each chapter to execute separately, so that changing code within one chapter won't require that all the code from all the other chapters be executed. But we might not want to have to go to the bother and extra typing of defining a new session for every chapter (like |\py[ch1]{|\meta{code}|}|). To do that, we could use |\restartpythontexsession{\thechapter}|. This would cause all sessions to restart whenever the chapter counter changes. If we wanted sessions to restart at each section within a chapter, we would use |\restartpythontexsession{\thechapter|\meta{delim}|\thesection}|. \meta{delim} is needed to separate the counter values so that they are not ambiguous (for example, we need to distinguish chapter 11-1 from chapter 1-11). Usually \meta{delim} should be a hyphen or an underscore; it must be a character that is valid in file names. +% +% Note that \textbf{counter values}, and not counters themselves, must be supplied as the argument. Also note that the command applies to \textbf{all} sessions. If it did not, then we would have to keep track of which sessions restarted when, and the lack of uniformity could easily result in errors on the part of the user. +% +% Keep in mind that when a session is restarted, all continuity is lost. It is best not to restart sessions if you need continuity. If you must restart a session, but also need to keep some data, you could save the data before restarting the session and then load the saved data after the restart. This approach should be used with \textbf{extreme} caution, since it can result in unanticipated errors due to sessions not staying synchronized.\footnote{For example, suppose sessions are restarted based on chapter. |session-ch1| saves a data file, and |session-ch2| loads it and uses it. You write the code, and run \pytex. Then you realize that |session-ch1| needs to be modified and make some changes. The next time \pytex\ runs, it will only execute |session-ch1|, since it detects no code changes in |session-ch2|. This means that |session-ch2| is not updated, at least to the extent that it depends on the data from |session-ch1|. Again, saving and loading data between restarted sessions, or just between sessions in general, can produce unexpected behavior. This can be avoided by using the \texttt{pytex.add\_dependencies()} method for all data that is loaded. It will ensure that all sessions stay in sync.} +% +% This command can only be used in the preamble. +% +% \DescribeMacro{\setpythontexoutputdir\marg{output~directory}} +% +% By default, \pytex\ saves all temporary files and automatically generated content in a directory called |pythontex-files-|\meta{sanitized jobname}, where \meta{sanitized jobname} is just |\jobname| with any space characters or asterisks replaced with hyphens. This directory will be created by |pythontex.py|. If we wish to specify another directory (for example, if |\jobname| is long and complex, and there is no danger of two files trying to use the same directory), then we can use the |\setpythontexoutputdir| macro to redefine the output directory.\footnote{In the rare event that both |\string\setpythontexoutputdir| is used and |\string\printpythontex| is needed in the preamble, |\string\setpythontexoutputdir| must be used first, so that |\string\printpythontex| will know where to look for output.} +% +% Any slashes in \meta{output~directory} should be forward slashes ``|/|'' (even under Windows). +% +% \DescribeMacro{\setpythontexworkingdir\marg{working~directory}} +% +% The \pytex\ working directory is the current working directory for \pytex\ scripts. This is the directory in which any open or save operations will take place, unless a path is explicitly specified. By default, the working directory is the same as the output directory. For example, if you are writing |my_file.tex| and save a matplotlib figure with |savefig('my_figure.pdf')|, then |my_figure.pdf| will be created in the output directory |pythontex-files-my_file|. But maybe you have a directory called |plots| in your document root directory. In that case, you could leave the working directory unchanged, and simply specify the relative path to |plots| when saving. Or you could set the working directory to |plots| using |\setpythontexworkingdir{plots}|, so that all content would automatically be saved there. If you want your working directory to be the document root directory, you should use a period (|.|) for \meta{working~directory}: |\setpythontexworkingdir{.}|. +% +% Any slashes in \meta{working~directory} should be forward slashes ``|/|'' (even under Windows). +% +% The working directory is automatically added to Python's |sys.path|, so that code in the working directory there may be imported without a path being specified. +% +% Note that in typical use scenarios, you should be able to use the output directory as the working directory. The |graphicx| package will automatically look for images and figures in the output directory, so long as you do not use the |\graphicspath| command outside the preamble.\footnote{\texttt{graphicx} looks for graphics in the document root directory and in the most recent graphics path defined by \texttt{\string\graphicspath}. \texttt{\string\graphicspath} stores the graphics path in \texttt{\string\Ginput@path}, overwriting any previous value. At the end of the preamble, \pytex\ appends the output directory to \texttt{\string\Ginput@path}. Thus, that directory will always be checked for graphics, so long as \texttt{\string\Ginput@path} is not overwritten by a subsequent use of \texttt{\string\graphicspath}. If you need to use \texttt{\string\graphicspath} within the document, you could consider creating a custom version that redefines \texttt{\string\Ginput@path} with the \pytex\ output directory automatically appended.} +% +% It is also possible to change the working directory from within Python code, via |os.chdir()|. +% +% +% \section{\texttt{depythontex}} +% \label{sec:depythontex} +% +% \pytex\ can greatly simplify the creation of documents. At the same time, by introducing dependence on non-\LaTeX\ external tools, it can constrain how these documents are used. For example, many publishers will not accept \LaTeX\ documents that require special packages or need special macros. To address this issue, the package includes a feature called |depythontex| that can convert a \pytex\ document into a plain \LaTeX\ document. +% +% +% \subsection{Preparing a document that will be converted} +% +% The conversion process should work flawlessly in most cases, with no special formatting required. +% +% For best results, keep the following in mind. +% \begin{itemize} +% \item The \pytex\ package should have its own |\usepackage|. +% \item Currently, |depythontex| only supports the standard \pytex\ commands and environments. Support for user-defined commands and environments that incorporate \pytex\ is planned for a future release. +% \item If you need to insert content from Python in inline contexts, it is best to use |\py| or an equivalent command. If you use |print|, either directly (for example, from within |\pyc|) or via |\printpythontex|, make sure that the spacing following the printed content is correct. You may need to print an |\endinput| or |%| at the end of your content to prevent an extra trailing space. |depythontex| will attempt to reproduce the spacing of the original document, even if it is not ideal. See Section~\ref{sec:depythontex:technical-details} for additional details. +% \item Some \LaTeX\ environments, such as the |verbatim| environment from the |verbatim| package and the |Verbatim| environment from |fancyvrb|, do not allow text to follow the |\end{|\meta{environment}|}|. If you bring Python-generated content that ends with one of these environments into your document, using |print| or |\py|, make sure that the end-of-environment command is followed by a newline. For example, if you are assembling a |Verbatim| environment to bring in, the last line should be the string +% \begin{verbatim} +% '\\end{Verbatim}\n' +% \end{verbatim} +% Even if you neglect a final newline, |depythontex| will still function correctly in most cases. Whenever Python-generated content does not end with a newline, |depythontex| usually inserts one and gobbles spaces that follow the environment. This preserves the correct spacing while avoiding any issues produced by an end-of-environment command. But in some cases, |depythontex| cannot do this. For example, if |\py| is used to bring in a |Verbatim| environment, and there is text immediately after the |\py|, without any intervening space, |depythontex| cannot substitute a newline for spaces, because there are none. Because of the way that |print| and |\py| content is brought in, everything may still work correctly in the original \pytex\ document. But it would fail in the |depythontex| output. +% \item Do not create \pytex\ commands or environments on the Python side and |print| or otherwise bring them in. That is too many levels of complexity! +% \item |depythontex| is only designed to replace \pytex\ commands and environments that are actually in the main document file. Do not bring in anything that contains \pytex\ commands or environments via |\input|, |\include|, or |\usepackage|. The only exception is \pytex\ commands and environments that do not typeset anything (for example, |code| environments that don't print). If these are brought in via a package or external file, the command |\DepythontexOff| must come before them, and they must be followed by the command |\DepythontexOn|. Basically, |depythontex| must be disabled for commands and environments brought in via external files. This works so long as the commands and environments only provide code and settings, rather than any typeset content. +% +% Tools for automatically removing the |\usepackage| for packages that contain \pytex\ commands will be added soon; for now, these |\usepackage|'s must be removed manually in the |depythontex| output. +% \item Keep in mind that the file produced by |depythontex| will need to include any graphics that you create with \pytex. Make sure any graphics are saved in a location where they are easily accessible. +% \end{itemize} +% +% \subsection{Removing \pytex\ dependence} +% +% Converting a document requires three steps. +% \begin{enumerate} +% \item Turn on the package option |depythontex|. Then compile the document, run |pythontex.py|, and compile the document again. Depending on the document, additional compiles may be necessary (for example, to resolve references). Any syntax highlighting will be turned off automatically during this process, to remove dependence on Pygments. +% +% During compilation, an auxiliary file called \meta{jobname}|.depytx| is created. This file contains information about the location of the \pytex\ commands and environments that need to be replaced, and about the content with which they are to be replaced. +% +% \item Run the |depythontex.py| script. This takes the following arguments. +% \begin{itemize} +% \item |--encoding ENCODING| This is the encoding of the \LaTeX\ file and all related files. If an encoding is not specified, UTF-8 is assumed. +% \item |--overwrite| This turns off the user prompt in the event that a file already exists with the output name, making overwriting automatic. +% \item |--listing| This option specifies the commands and environments that are used for any typeset code. This can be |verbatim|, |fancyvrb|, |listings|, |minted|, or |pythontex|.\footnote{The \texttt{pythontex} option is included for completeness. In most cases, you would probably use \texttt{depythontex} to remove all dependence on \pytex. But sometimes it might be useful to remove all Python code while still using \pytex\ for syntax highlighting.} |verbatim| is used by default. An appropriate |\usepackage| command is automatically added to the output document's preamble. +% +% When code is typeset with any option other than |verbatim|, listing line numbering from the original document will be preserved. When code is typeset with any option other than |verbatim| and |fancyvrb|, syntax highlighting will also be preserved. The only exception is when |listings| is used, and |listings|'s language name does not correspond to Pygments' lexer name. In this case, you should use the |--lexer-dict| option to specify how the Pygments lexer is to be translated into a |listings| language. +% \item |--lexer-dict| This option is used to specify how Pygments lexers are converted to |listings| languages, when the two do not have the same name. It takes a comma-separated list of the form +% \begin{verbatim} +% "<Pygments lexer>:<listings language>, ..., ... " +% \end{verbatim} +% A Python-style dict will also be accepted. +% \item |--preamble| This option allows additional commands to be added to the output document's preamble. This is useful when you want the output document to load a package that was automatically loaded by \pytex, such as |upquote|. +% \item |--graphicspath| This option adds the |outputdir| to any existing graphics path defined by |\graphicspath|, or adds a |\graphicspath| command if one does not already exist. This causes the |depythontex| document to automatically look in the |outputdir| for graphics. Only use this option if you want to continue using the |outputdir| with the |depythontex| document. Graphics are further discussed below. +% \item |TEXNAME| The name of the \LaTeX\ file whose \pytex\ dependence is to be removed. +% \item |[OUTFILE]| By default, the script takes a file |<filename>.<ext>| and creates a new file called |depythontex_<filename>.<ext>|. If a name is given for the output file, that is used instead. +% \end{itemize} +% \item Compile the |depythontex| file, and compare it to the original. +% +% The original and |depythontex| files should be nearly identical. All Python-generated content is substituted directly, so it should be unchanged. Usually, any differences will be due to changes in the way that code is typeset. For example, by default all code in the |depythontex| file is typeset with |\verb| and |verbatim|. But |\verb| is more fragile than the inline \pytex\ commands (it isn't allowed inside other commands), and |verbatim| does not support line numbering or syntax highlighting. +% +% Remember that the |depythontex| file will need to include any graphics created by \pytex. By default, these are saved in the \pytex\ |outputdir|, which is |pythontex-files-<jobname>| unless you have customized it. They may be in other locations if you have set a non-default |workingdir| or have specified a path when saving graphics. Depending on your needs and configuration, you may wish to copy the graphics into a new location or specify their location via |\graphicspath|. Or you can run |depythontex| with the |--graphicspath| option, which will add the |outputdir| to any existing usage of |\graphicspath|, or add a |\graphicspath| command if one does not already exist.\footnote{Keep in mind that any time \texttt{\string\graphicspath} is used, it overwrites any previously specified path. If your document is using \texttt{\string\graphicspath} at multiple points in the preamble, or using it anywhere outside the preamble, then the \texttt{-{}-graphicspath} option will fail due to the path being overwritten.} +% +% Depending on your needs, you may wish to customize |depythontex.py|. The actual substitutions are performed in a few functions that are defined at the beginning of the script. +% \end{enumerate} +% +% +% \subsection{Technical details} +% \label{sec:depythontex:technical-details} +% +% The |depythontex| process should go smoothly under most circumstances, and the document produced usually should not need manual tweaking. There are a few technical details that may be of interest. +% \begin{itemize} +% \item Content that is printed (actually printed, not from a command like |\py|) is always followed by a space when included as \LaTeX\ code rather than as verbatim. Usually this is only noticeable when the content is used inline, adjacent to other text. In such cases, you need to make sure that the spacing is correct in your original document, and need to be aware of how |depythontex| handles the conversion. + +% This spacing behavior is due to \LaTeX's |\input|. When the file of printed content is brought in via |\input|, \LaTeX\ removes any newline characters (|\n|, |\r|, or |\r\n|) at the end of each line, and adds a space at the end of each line (even if there wasn't a newline character). Thus, when the printed content is brought in, a space is added to its end. Since this space is within the |\input|'s curly braces |{}|, it is not combined with any following spaces in the \LaTeX\ document to make a single space. Rather, if the printed content is followed by one or more spaces, two spaces will result; and if it is followed immediately by text, there will be a single space before the text. +% +% The space added by |\input| is often invisible, and even when it is not, it is sometimes desirable.\footnote{For example, |\string\printpythontex| behaves as a normal command, and gobbles following spaces, but the space from |\string\input| puts a space back. So you often get the space you want in inline contexts.} But this space can be an issue in some inline contexts. The simplest solution is to use a command like |\py| to bring in content inline. +% +% If a command like |\py| is not practical for some reason, there are at least three ways to deal with the space introduced by |\input|: by printing |\endinput| at the end of the printed content (ending the content before the final space), by printing |%| at the end of the printed content (commenting out the final space), or by using |\unskip| after the printed content (eating preceding spaces). |depythontex| will work with all three approaches, but only under a limited range of circumstances. In summary, |depythontex| works with |\endinput| and |%| only if they are the very last thing printed (before a final newline), and works with a following |\unskip|.\footnote{It would be possible to make \texttt{depythontex} work with \texttt{\string\endinput} and \texttt{\%} anywhere, not just at the very end of printed content. But doing so would require a lot of additional parsing, especially for \texttt{\string\endinput}, to be absolutely sure that we found an actual command rather than a string. Furthermore, there is no reason that there should be any content after an \texttt{\string\endinput} or \texttt{\%}, since such content would never be included in the document. Indeed, the current approach prevents any printed content from accidentally being eliminated in this manner.} +% \begin{itemize} +% \item |\endinput| cannot be left in the printed content that is substituted into the new document, because it would cause the new document to end immediately. |depythontex| checks the very end of printed content for |\endinput|, and removes it if it is there before substituting the content. The terminating |\endinput| is only removed if it is not a string, |\string\endinput|. +% +% If |\endinput| is anywhere else in the printed content, and it is not immediately preceded by |\string|, |depythontex| issues a warning. +% \item A terminating |%| cannot be left in the printed content that is substituted into the new document, because it would comment out any text in the remainder of the line into which it is substituted (in |\input|, its effect is limited to the print file). |depythontex| checks the very end of printed content for |%|, and removes it if it is there before substituting the content. |depythontex| only removes the terminating |%| if it is not a literal character |\%| or |\string%|. +% +% |depythontex| checks the last line of printed content for other |%| characters, and issues a warning if there are any |%| characters that are not part of |\%| or |\string%|. +% +% \item A following |\unskip| could be left in the new document, since it would not produce incorrect spacing. But it would be undesirable, since it was only there in the first place because of the way that \pytex\ was used. |depythontex| checks for |\unskip|, and if it is found, attempts to correct the spacing and remove the |\unskip|. This removal process is only possible if |\unskip| immediately follows a command (otherwise, it wouldn't work anyway) or is on the line immediately after the end of an environment. +% +% If |depythontex| finds |\unskip| following printed content, but cannot replace it (it doesn't immediately follow a command, or isn't on the line immediately after the end of an environment), a warning is issued. It is possible that the |\unskip| is not correctly positioned, and even if it produces the correct spacing, the user should know that due to its location it will survive in the converted document. +% \end{itemize} +% +% If one of the above approaches is not used to eliminate the space introduced by the final newline in printed content, |depythontex| still makes sure that the spacing in the new document matches that of the original document, even if that means forcing a double space. In the majority of cases, |depythontex| can create the correct spacing using actual spaces and newlines. But in a few instances, it will include a |\space{}| to ensure a double space that matches the original document. In those situations, a warning is issued in case the spacing was not intentional. +% +% \item Strings such as |\\}|, |\\{|, and |\string| can occur in \pytex\ content that is being replaced. It is possible that they might decrease performance somewhat in larger or more complex documents. +% +% \pytex\ commands for entering code allow the code to be delimited with either matched braces |{}| or with a repeated character such as |#| (as in |\verb|). Any verbatim code delimited by braces cannot \textbf{contain} any braces \textbf{unless} they are paired. So it is easy for |depythontex.py| to find the end of the delimited code. +% +% However, |depythontex.py| must also replace \pytex\ commands that take a normal, non-verbatim argument delimited by braces (for example, the various |\setpythontex...| commands). Finding the closing brace for these commands is usually straightforward, but it can be tricky because the argument might contain a literal brace such as |\}| or |\string}|. |depythontex.py| automatically accounts for |\}|. If it detects |\string|, it also accounts for it, but doing so requires more intense parsing. Similarly, |\\}| requires extra parsing, because depending on what comes before it, the first backslash |\| could be literal (for example, if preceded by |\string|), or the two backslashes |\\| could go together to indicate a new line. +% \end{itemize} +% +% +% +% +% +% \section{\LaTeX\ programming with \pytex} +% +% This section will be expanded in the future. For now, it offers a brief summary. +% +% \subsection{Macro programming with \pytex} +% +% In many situations, you can use \pytex\ commands inside macro definitions without any special consideration. For example, consider the following macro, for calculating powers. +% \newcommand{\pow}[2]{\py{#1**#2}} +% \begin{verbatim} +% \newcommand{\pow}[2]{\py{#1**#2}} +% \end{verbatim} +% Once this is defined, we can calculate |2**8| via |\pow{2}{8}|: \pow{2}{8}. Similarly, we can reverse a string. +% \newcommand{\reverse}[1]{\py{"#1"[::-1]}} +% \begin{verbatim} +% \newcommand{\reverse}[1]{\py{"#1"[::-1]}} +% \end{verbatim} +% Now we can use |\reverse{``This is some text!''}|: \reverse{``This is some text!''}. +% +% Such approaches will break down when some special \LaTeX\ characters such as percent |%| and hash |#| must be passed as arguments. In such cases, the arguments need to be captured verbatim. The |xparse| and |newverbs| packages provide commands for creating macros that capture verbatim arguments. You could also consult the \pytex\ implementation, particularly the implementation of the inline commands. In either case, you may need to learn about \TeX's catcodes and tokenization, if you aren't already familiar with them. +% +% Of course, there are many cases where macros don't need arguments. Here is code for creating a macro that generates random polynomials. +% \begin{verbatim} +% \begin{sympycode} +% from sympy.stats import DiscreteUniform, sample +% x = Symbol('x') +% a = DiscreteUniform('a', range(-10, 11)) +% b = DiscreteUniform('b', range(-10, 11)) +% c = DiscreteUniform('c', range(-10, 11)) +% def randquad(): +% return Eq(sample(a)*x**2 + sample(b)*x + sample(c)) +% \end{sympycode} +% \newcommand\randquad{\sympy{randquad()}} +% \end{verbatim} +% +% If you are considering writing macros that involve \pytex, you should keep a few things in mind. +% \begin{itemize} +% \item Do you really need to use \pytex? If another package already provides the functionality you need, it may be simpler to use an existing tool, particularly if you are working with special characters and thus need to capture verbatim arguments. +% \item A feature called |depythontex| has recently been added. It creates a copy of the original \LaTeX\ document in which all \pytex\ commands and environments are replaced by their output, so that the new document does not depend on \pytex\ at all. This is primarily of interest for publication, since publishers tend not to like special packages or macros. |depythontex| does not yet support custom user commands. So if you decide to create custom macros now, and expect to need |depythontex|, you should expect to have to edit your macros before they will work with |depythontex|. +% \end{itemize} +% +% \subsection{Package writing with \pytex} +% +% As of v0.10beta, the custom code command and environment, and the regular code command and environment, work in the preamble. This means that it is now possible to write packages that incorporate \pytex! At this point, packages are probably a good way to keep track of custom code that you use frequently, and maybe some macros that use \pytex. +% +% However, you are encouraged not to develop a huge mathematical or scientific package for \LaTeX\ using \pytex. At least not yet! As discussed above, |depythontex| will bring changes to macro programming involving \pytex. So have fun writing packages if you want---but keep in mind that \pytex\ will keep changing, and some things that are difficult now may be very simple in the future. +% +% +% \section{Support for additional languages} +% \label{sec:other-languages} +% +% Beginning with v0.12, it is much simpler to add support for languages beyond Python. Support for several additional languages will be added in coming months. +% +% In the immediate future, support for additional languages will be part of \pytex. Later, it may make sense to provide an alternative interface for other languages. For example, a package could be created that provides access to \pytex\ internals in a language-agnostic manner, without having the word ``python'' as part of the command names. +% +% Languages beyond Python will typically not be enabled by default, to prevent potential macro naming conflicts with other packages. At least two possible base names for commands and environments will be provided for each language. Typically these will be the name of the language and the language's file extension. For example, Ruby has the |ruby| and |rb| base names. You can choose which base name to use for creating a family of commands and environments based on personal preference and potential naming conflicts. +% +% +% \subsection{Ruby} +% +% Support for Ruby was added in v0.12. Ruby support should be almost at the same level as that for Python. +% +% The utilities class is called |RubyTeXUtils|, and the class instance is |rbtex|. The variables and methods are the same as those for Python (Section~\ref{sec:usage:commands:utilities-class}), except that there is not currently a |set_formatter()| method. (The Python utilities class has the special SymPy formatter, but there aren't yet any specialized formatters for Ruby.) +% +% A family of commands and environments for Ruby is not created by default. Two base names are provided for families: |ruby| and |rb|. Preconfigured families for these names may be created via the |usefamily| package option. Keep in mind that a |ruby| command is defined as part of the Ruby package in the \href{http://www.ctan.org/pkg/cjk}{CJK package}. I am unaware of a package that provides an |\rb| command. +% +% Ruby exceptions are synchronized with the document, but the line numbering does not always correspond to the Python equivalent. For example, suppose that |\pyc{1+}| is on line 10 of a document. The SyntaxError will then be synchronized with line 10. If |\rubyc{1+}| were on the same line, the resulting error would be synchronized with line 11. This is because Ruby allows addition to continue on subsequent lines of code, so an error is only raised when the next line of code that is executed does not contain a number (there is always template code after user code). +% +% +% \subsection{Julia} +% +% Support for Julia was added in v0.12. Julia support should be at almost the same level as that for Python. The format of Julia stderr is somethat different from that of Python and Ruby. This required a modified parsing and synchronization algorithm. The current system is functional but will likely change somewhat in the future. +% +% The utilities class is called |JuliaTeXUtils|, and the class instance is |jltex|. The variables and methods are the same as those for Python (Section~\ref{sec:usage:commands:utilities-class}), except that there is not currently a |set_formatter()| method. (The Python utilities class has the special SymPy formatter, but there aren't yet any specialized formatters for Julia.) +% +% A family of commands and environments for Ruby is not created by default. Two base names are provided for families: |julia| and |jl|. Preconfigured families for these names may be created via the |usefamily| package option. Keep in mind that Pygments only added Julia support in version 1.6, so you may need to update your Pygments installation, or just change the default lexer. +% +% Julia exceptions are synchronized with the document, but the line numbering does not always correspond to the Python equivalent. This is because Julia allows expressions to be continued on subsequent lines in ways that Python does not. +% +% +% \subsection{Adding support for a new language} +% +% Adding support for an additional language involves creating two templates, creating a new instance of a class, and using a \pytex\ macro. In some cases, additional changes may be necessary for full support. The information below does not deal with creating |console| families; additional support for user-defined |console| families will be added in the future. +% +% The system for adding languages should be relatively stable, but is subject to change as additional languages with additional requirements are added. The current system is sufficient for Python and similar languages. Languages with less regular |stderr| may require additional features and may not be able to have full synchronization between |stderr| and the \LaTeX\ document. Keep in mind that if \pytex\ is unable to classify exceptions as errors or warnings, it treats them as errors or warnings based on the script exit status. +% +% It may be helpful to refer to |pythontex_engines.py|, specifically the templates and utilities classes, while reading the section below. +% +% \subsubsection{Template} +% +% \pytex\ executes user code by inserting it in a script template. Replacement fields in the template are indicated by double curly braces: |{{|\meta{field}|}}|. Space between \meta{field} and the braces is allowed; |{{ |\meta{field}| }}| is valid. Replacement fields (\textbf{including} the braces) should be surrounded by quotation marks or equivalent when the replacement is to be a string rather than literal code. +% +% The template should perform the following tasks. +% \begin{itemize} +% \item Set the script encoding. The |{{encoding}}| field will be replaced with a user-specified encoding or the default |UTF-8|. If you are not using anything beyond ASCII, this is not strictly necessary. +% \item Python templates should have a |{{future}}| field at the beginning, for compability with Python 2 and the package option |pyfuture|.\footnote{The beginning of user code is parsed for imports from \texttt{\_\_future\_\_}. Any imports are collected and inserted into the \texttt{\{\{future\}\}} field.} +% \item Set the |stdout| and |stderr| encoding, again using |{{encoding}}|. As before, this is not strictly necessary when only ASCII support is needed. +% \item Create a language-specific equivalent of the \pytex\ utilities class.\footnote{Python templates can import the \pytex\ utilities class. In that case, \texttt{sys.path.append('\{\{utilspath\}\}')} is needed before the import, so that the location of the utilities class is known.} Create an instance of this class. It is recommended that the class be called \meta{language~name}|TeXUtils| and the instance \meta{language~extension}|tex|, by analogy with the Python case.\footnote{The class could be called \meta{language~name}\texttt{TeX}. In that case, the class and the instance would have the very same name (except for capitalization) in cases where the language name and extension are the same (for example, Lua). That is probably not desirable, and besides, \texttt{Utils} adds additional clarity. The instance name \meta{language~extension}\texttt{tex} is recommended because it will be short and easily remembered. Plain \texttt{tex} could be used instead, but that would be less descriptive (it lacks the interface connotations) and would not remind the user of the language currently in use (which could be beneficial in a document combining multiple languages, each with its own slightly different utilities class).} When the \meta{language~extension} is only a single character or is shared by multiple languages, it may be better to use the full \meta{language~name} or an abbreviation in the name of the class instance. +% +% For full \pytex\ support, the utilities class should provide the following methods: +% \begin{itemize} +% \item |formatter()|: For formatting content for inline commands equivalent to |\py|. This should take a single argument of any type. By default, it should return a standard string representation of its argument. +% \item |before()| and |after()|: Initially, these should do nothing; they are provided to be redefined by the user. They should take no arguments. +% \item |add_dependencies()| and |add_created()|: These should accept an arbitrary number of comma-separated strings (if supported by the language). Each method should append its arguments to a list or equivalent data structure, for later use. +% \item |cleanup()|: This prints a dependencies delimiter string |{{dependencies_delim}}| to |stdout|, then prints all dependencies (one per line), then prints a created files delimiter string |{{created_delim}}|, then prints all created files (one per line). The delimiters should be printed even if there are no dependencies or created files. The delimiters contain no backslashes or quotes. +% \end{itemize} +% The utilities class should also provide several variables, as described below. +% \item Attempt to change to the working directory |{{workingdir}}|. Raise an error and exit if this is not possible. For convenience, the script should check for a |--manual| command line argument. If this argument is present, the script should proceed even if the working directory cannot be found. This allows the user to manually invoke the script for debugging (the script can be saved via |keeptemps|). +% +% The working directory should be added to the module search path (Python |sys.path|, Ruby |$:| or |$LOAD_PATH|, etc.), unless it is the same as the document root directory or is otherwise already on the module search path. +% \item For full compatibility, the template should have an |{{extend}}| field where additional module imports or other code may be inserted. This allows a basic template to be a created for each language. The basic template may then be customized for specific purposes. The |{{extend}}| field should be after the utilities class instance has been created, so that the workings of the utilities class (|formatter()|, |before()|, |after()|, etc.) may be customized by it. +% \item \LaTeX-related variables of the utilities class instance that do not change should be set. These use the fields |{{input_family}}|, |{{input_session}}|, and |{{input_restart}}|; all should be strings. These variables should be named after the fields if possible (for example, |pytex.input_family|). These variables are not strictly necessary, but they allow user code to access information about its \LaTeX\ context. +% \item There should be a |{{body}}| field where the body of the script is inserted. +% \item The script should end by calling the |cleanup()| method. +% \end{itemize} +% +% \subsubsection{Wrapper} +% Each chunk of user code is inserted into a wrapper template. This performs the following tasks. +% \begin{itemize} +% \item Set additional \LaTeX-related utilities variables: |{{input_command}}|, |{{input_context}}|, |{{input_args}}|, |{{input_instance}}|, |{{input_line}}|. All of these should be strings. They are not required, but make possible closer \LaTeX\ integration. |{{input_context}}| and |{{input_args}}| are not yet supported on the \LaTeX\ side, but will allow information about the \LaTeX\ context (for example, page width) and arguments from \LaTeX\ commands to be passed to user code. +% \item Write a delimiter |{{stdout_delim}}| to |stdout| and a delimiter |{{stderr_delim}}| to |stderr|. Both delimiters should be strings. Both should be written in such a way that the delimiter is followed by a newline; the delimiters that are inserted in the wrapper template \textbf{do not} contain a newline. For example, something like |"{{stderr_delim}}\n"| might be necessary. The delimiters contain no backslashes or quotation marks. +% \item Call |before()|. +% \item Have a |{{code}}| field into which the current chunk of user code is inserted. +% \item Call |after()|. For languages like Ruby that allow statements to continue onto subsequent lines, without enclosing parentheses or other delimiters, the call to |after()| should immediately follow user code, without any intervenening empty lines. That way, any syntax errors from the last line of user code will be caught as early as possible, and the corresponding line numbers will be more meaningful. +% \end{itemize} +% +% \subsubsection{The \texttt{CodeEngine} class} +% The final step in adding support for a language is creating a new instance of the |CodeEngine| class. The |CodeEngine| class manages the process of inserting user code into code templates and creates the records needed for synchronizing |stderr| with the document. +% +% A new |CodeEngine| instance is initialized with the following arguments. All arguments are strings unless noted otherwise. +% \begin{itemize} +% \item The instance name. This will be the base name for commands and environments that use the instance. For example, |\py|, |\pyc|, |pycode|, etc., rely on the |py| instance of the |CodeEngine| class. +% \item The name of the language. In some cases, this may be the same as the instance name. +% \item The filename extension for scripts (with or without a period). +% \item The command for running scripts. The script that is executed should be referred to as ``|{{file}}.|\meta{extension}'' (without the quotes).\footnote{It might seem that the extension is redundant, since it is specified separately. The command is specified in this form to simplify cases where there may be intermediary files in the execution process.} The interpreter may be hardcoded (|python {{file}}.py|), but it is best to leave it as a substitution field (|{{python}} {{file}}.py|) so that the |--interpreter| command-line option can be used to provide a specific interpreter. +% \item The script template. +% \item The wrapper template. +% \item A template that specifies how code from commands like |\py| should be inserted into a call to the |formatter()| method. The user code is specified by |{{code}}|. The output of the |formatter()| method should be written to |stdout|, so something like |'print(pytex.formatter({{code}}))'| is needed. +% \item An optional list of strings (or an individual string) that gives patterns for identifying error messages. +% \item An optional list of strings (or an individual string) that gives patterns for identifying warning messages. +% \item An optional list of strings (or an individual string) that gives patterns for identifying code line numbers in |stderr|. These patterns use the field |{{number}}|. These patterns are searched for in any line of |stderr| that contains the name of the script that was executed. +% \item An optional boolean that specifies whether the engine emulates an interactive console. Currently, user-defined engines that emulate consoles are not supported. +% \item An optional string of startup commands for engines that emulate consoles. +% \item An optional list of strings (or an individual string) that specifies any files created during execution, beyond the script |{{file}}.|\meta{extension}. The field |{{file}}| may be used in file names. +% \end{itemize} +% +% An example of creating the |py| engine is shown below. The |python_template| and |python_wrapper| are long enough that they are defined separately. +% \begin{verbatim} +%CodeEngine('py', 'python', '.py', 'python {{file}}.py', +% python_template, python_wrapper, +% 'print(pytex.formatter({{code}}))', +% 'Error:', 'Warning:', ['line {{number}}', ':{{number}}:']) +% \end{verbatim} +% The script template and wrapper templates may be defined with Python's triple-quoted strings. All content within such a string may be indented for clarity, as can be seen in |pythontex_engines.py|. Strings are automatically dedented when |CodeEngine| instances are created. +% +% In addition to the |CodeEngine| class, there is also a |SubCodeEngine| class. It allows a new engine to be created based on an existing engine. It requires the name of the engine from which to inherit and the name of the new engine. All of the other arguments listed above are optional; if any are provided, they overwrite the inherited arguments. The class also takes one additional optional argument, |extend|. This is a string that specifies additional code to be entered in the inherited template, in the |{{extend}}| field. Subengines of subengines may be created; in that case, any |extend|s are cumulative. +% +% \subsubsection{Creating the \LaTeX\ interface} +% Once a new engine has been created, access from the \LaTeX\ side must be provided. \pytex\ provides a macro for this purpose. +% +% \DescribeMacro{\makepythontexfamily\oarg{options}\marg{engine}} +% +% This command creates a non-|console| family of commands and environments for \meta{engine}: |code|, |block|, and |verbatim| commands and environments, and an inline command like |\py|. +% +% \textbf{This command is appropriate for user-defined languages, but it is preferable (and more convenient) to use the package option |usefamily| when using an engine that is included with \pytex.} The package option will create a preconfigured family in which things such as the appropriate Pygments lexer have already been set. +% +% \meta{options} allows |prettyprinter|, |pyglexer|, and |pygopt| to be specified for the family. +% +% +% \section{Troubleshooting} +% \label{sec:troubleshooting} +% +% \begin{itemize} +% \item If a \pytex\ document will not compile, you may want to delete the directory in which \pytex\ content is stored and try compiling from scratch. It is possible for \pytex\ to become stuck in an unrecoverable loop. Suppose you tell Python to print some \LaTeX\ code back to your \LaTeX\ document, but make a fatal \LaTeX\ syntax error in the printed content. This syntax error prevents \LaTeX\ from compiling. Now suppose you realize what happened and correct the syntax error. The problem is that the corrected code cannot be executed until \LaTeX\ correctly compiles and saves the code externally, but \LaTeX\ cannot compile until the corrected code has already been executed. One solution in such cases is to correct the code, delete all files in the \pytex\ directory, compile the \LaTeX\ document, and then run \pytex\ from scratch. You can also disable the inclusion of printed content using the |debug| package options. +% +% \item Dollar signs \$ may appear as \textsterling\ in italic code comments typeset by Pygments. This is a font-related issue. One fix is to |\usepackage[T1]{fontenc}|. +% \end{itemize} +% +% +% \section{The future of \pytex} +% \label{sec:future} +% +% This section consists of a To Do list for future development. The To Do list is primarily for the benefit of the author, but also gives users a sense of what changes are in progress or under consideration. +% +% \pytxtodo{ +% This section consists of a To Do list and a roadmap for future development. The To Do list is primarily for the benefit of the author, but also gives users a sense of what changes are in progress. The roadmap provides a projected direction for future development.} +% +% \subsection{To Do} +% +% \subsubsection{Modifications to make} +% \begin{itemize} +% \item Add support for |depythontex| to remove the |\usepackage| for a package that contains \pytex\ commands and environments. +% \item Add better support for macro programming, including |depythontex| support for user-defined commands and environments. +% \item Add Pygments commands and environments that are compatible with basic |listings| and |minted| syntax. This will make it easier to work with documents converted to \LaTeX\ from another format, for example via Pandoc. +% \item User-defined custom commands and environments for general Pygments typesetting. +% \item Additional documentation for the Python code (Sphinx?). +% \item Improved testing framework. +% \item It might nice to include some methods in the \pytex\ utilities for formatting numbers (especially with SymPy and Pylab). +% \item Test the behavior of files brought in via |\input| and |\include| that contain \pytex\ content. +% \item Continue adding support for additional languages. In preparation: Julia. Under consideration: Perl, Lua, MATLAB, Mathematica, Sage, R, Octave. +% \end{itemize} +% +% \subsubsection{Modifications to consider} +% \begin{itemize} +% \item Consider fixing error line number synchronization with Beamer (and other situations involving error lines in externalized files). The |filehook| and |currfile| packages may be useful in this. One approach may be to patch the macros associated with |\beamer@doframeinput| in |beamerbaseframe.sty|. Note: Beamer's |fragile=singleslide| option makes this much less of an issue. This is low priority. +% \item Allow \LaTeX\ in code, and expand \LaTeX\ macros before passing code to |pythontex.py|. Maybe create an additional set of inline commands with additional |exp| suffix for |expanded|? This can already be done by creating a macro that contains a \pytex\ macro, though. +% \item Built-in support for background colors for blocks and verbatim, via |mdframed| or a similar package? +% \item Support for executing external scripts, not just internal code? It would be nice to be able to typeset an external file, as well as execute it by passing command-line arguments and then pull in its output. +% \item Is there any reason that saved printed content should be allowed to be brought in before the code that caused it has been typeset? Are there any cases in which the output should be typeset \textbf{before} the code that created it? That would require some type of external file for bringing in saved definitions. +% \item Consider some type of primitive line-breaking algorithm for use with Pygments. Could break at closest space, indent 8 spaces further than parent line (assuming 4-space indents; could auto-detect the correct size), and use \LaTeX\ counter commands to keep the line numbering from being incorrectly incremented. Such an approach might not be hard and might have some real promise. +% \item Consider allowing names of files into which scripts are saved to be specified. This could allow \pytex\ to be used for literate programming, general code documentation, etc. Also, it could allow writing a document that describes code and also produces the code files, for user modification (see the |bashful| package for the general idea). Doing something like this would probably require a new, slightly modified interface to preexisting macros. +% \end{itemize} +% +% +% \pytxtodo{ +% \subsection{Roadmap} +% \label{sec:future:roadmap} +% +% Under development. For now, see the To Do list. +% } +% +% +% +% \section*{Acknowledgements} +% +% Thanks to Nicholas Lu Chee Seng for help testing the earliest versions. +% +% Thanks to Øystein Bjørndal for many suggestions and for help with OS X compatibility. +% +% +% \PrintChangelog +% +% \StopEventually{\PrintIndex} +% +% \section{Implementation} +% +% \iffalse +%<*package> +% \fi +% +% This section describes the technical implementation of the package. Unless you wish to understand all the fine details or need to use the package in extremely sophisticated ways, you should not need to read it. +% +% The prefix |pytx@| is used for all \pytex\ macros, to prevent conflict with other packages. Macros that simply store text or a value for later retrieval are given names completely in lower case. For example, |\pytx@packagename| stores the name of the package, |PythonTeX|. Macros that actually perform some operation in contrast to simple storage are named using CamelCase, with the first letter after the prefix being capitalized. For example, |\pytx@CheckCounter| checks to see if a counter exists, and if not, creates it. Thus, macros are divided into two categories based on their function, and named accordingly. +% +% \subsection{Package opening} +% We store the name of the package in a macro for later use in warnings and error messages. +% \begin{macrocode} +\newcommand{\pytx@packagename}{PythonTeX} +\newcommand{\pytx@packageversion}{v0.12} +% \end{macrocode} +% +% \subsection{Required packages} +% A number of packages are required. |fancyvrb| is used to typeset all code that is not inline, and its internals are used to format inline code as well. |etex| provides extra registers, to avoid the (probably unlikely) possibility that the many counters required by \pytex\ will exhaust the supply. |etoolbox| is used for string comparison and boolean flags. |xstring| provides string manipulation. |pgfopts| is used to process package options, via the |pgfkeys| package. |newfloat| allows the creation of a floating environment for code listings. |currfile| is needed to allow errors and warnings to be synchronized with content brought in via |\input|, |\include|, etc. |xcolor| or |color| is needed for syntax highlighting with Pygments. +% \begin{macrocode} +\RequirePackage{fancyvrb} +\RequirePackage{etex} +\RequirePackage{etoolbox} +\RequirePackage{xstring} +\RequirePackage{pgfopts} +\RequirePackage{newfloat} +\RequirePackage{currfile} +\AtBeginDocument{\@ifpackageloaded{color}{}{\RequirePackage{xcolor}}} +% \end{macrocode} +% +% \subsection{Package options} +% We now proceed to define package options, using the |pgfopts| package that provides a package-level interface to |pgfkeys|. All keys for package-level options are placed in the key tree under the path |/PYTX/pkgopt/|, to prevent conflicts with any other packages that may be using |pgfkeys|. +% +% +% \subsubsection{Enabling command and environment families} +% +% \begin{macro}{\pytx@families} +% This option determines which command and environment families are defined beyond |py|, |pylab|, and |sympy|. Additional families are not automatically defined since some of them create commands or environment that may conflict with other packages.\footnote{For example, a \texttt{\string\ruby} command for Ruby code, and the \texttt{\string\ruby} command defined by the Ruby package in the \href{http://www.ctan.org/pkg/cjk}{CJK package}.} +% \begin{macrocode} +\def\pytx@families{} +\pgfkeys{/PYTX/pkgopt/usefamily/.estore in=\pytx@families} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Gobble} +% \begin{macro}{\pytx@opt@gobble} +% This option determines how leading whitespace in user code is treated. +% \begin{macrocode} +\def\pytx@opt@gobble{none} +\pgfkeys{/PYTX/pkgopt/gobble/.is choice} +\pgfkeys{/PYTX/pkgopt/gobble/none/.code=\def\pytx@opt@gobble{none}} +\pgfkeys{/PYTX/pkgopt/gobble/auto/.code=\def\pytx@opt@gobble{auto}} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Beta} +% \begin{macro}{pytx@opt@beta} +% This option provides compatibility with the beta releases from before the full v0.11 release. It should be removed after a few major releases. +% \begin{macrocode} +\newbool{pytx@opt@beta} +\pgfkeys{/PYTX/pkgopt/beta/.default=true} +\pgfkeys{/PYTX/pkgopt/beta/.is choice} +\pgfkeys{/PYTX/pkgopt/beta/true/.code=\booltrue{pytx@opt@beta}} +\pgfkeys{/PYTX/pkgopt/beta/false/.code=\boolfalse{pytx@opt@beta}} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Runall} +% \begin{macro}{pytx@opt@rerun} +% This option causes all code to be executed, regardless of whether it has been modified. It is primarily useful for re-executing code that has not changed, when the code depends on external files that \textbf{have} changed. Since it shares functionality with the |rerun| option, both options share a single macro. Note that the macro is initially set to |default|, rather than the default value of |errors|, so that the Python side can distinguish whether a value was actually set by the user on the \TeX\ side, and thus any potential conflicts between command-line options and package options can be resolved in favor of package options. +% \begin{macrocode} +\def\pytx@opt@rerun{default} +\pgfkeys{/PYTX/pkgopt/runall/.default=true} +\pgfkeys{/PYTX/pkgopt/runall/.is choice} +\pgfkeys{/PYTX/pkgopt/runall/true/.code=\def\pytx@opt@rerun{always}} +\pgfkeys{/PYTX/pkgopt/runall/false/.code=\relax} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Rerun} +% +% This option determines the conditions under which code is rerun. It stores its state in a macro shared with |runall|. +% \begin{macrocode} +\pgfkeys{/PYTX/pkgopt/rerun/.is choice} +\pgfkeys{/PYTX/pkgopt/rerun/never/.code=\def\pytx@opt@rerun{never}} +\pgfkeys{/PYTX/pkgopt/rerun/modified/.code=\def\pytx@opt@rerun{modified}} +\pgfkeys{/PYTX/pkgopt/rerun/errors/.code=\def\pytx@opt@rerun{errors}} +\pgfkeys{/PYTX/pkgopt/rerun/warnings/.code=\def\pytx@opt@rerun{warnings}} +\pgfkeys{/PYTX/pkgopt/rerun/always/.code=\def\pytx@opt@rerun{always}} +\pgfkeys{/PYTX/pkgopt/rerun/all/.code=\def\pytx@opt@rerun{always}% + \PackageWarning{\pytx@packagename}{rerun=all is deprecated; use rerun=always}} +% \end{macrocode} +% +% +% \subsubsection{Hashdependencies} +% +% \begin{macro}{pytx@opt@hashdependencies} +% This option determines whether dependencies (either code to be highlighted, or dependencies such as data that have been specified within a session) are checked for modification via modification time or via hashing. +% \begin{macrocode} +\def\pytx@opt@hashdependencies{default} +\pgfkeys{/PYTX/pkgopt/hashdependencies/.is choice} +\pgfkeys{/PYTX/pkgopt/hashdependencies/.default=true} +\pgfkeys{/PYTX/pkgopt/hashdependencies/true/.code=\def\pytx@opt@hashdependencies{true}} +\pgfkeys{/PYTX/pkgopt/hashdependencies/false/.code=\def\pytx@opt@hashdependencies{false}} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Autoprint} +% \begin{macro}{pytx@opt@autoprint} +% The |autoprint| option determines whether content printed within a code command or environment is automatically included at the location of the command or environment. If the option is not used, |autoprint| is turned on by default. If the option is used, but without a setting (|\usepackage[autoprint]{pythontex}|), it is true by default. We use the key handler \meta{key}|/.is choice| to ensure that only true/false values are allowed. The code for the true branch is redundant, but is included for symmetry. +% \begin{macrocode} +\newbool{pytx@opt@autoprint} +\booltrue{pytx@opt@autoprint} +\pgfkeys{/PYTX/pkgopt/autoprint/.default=true} +\pgfkeys{/PYTX/pkgopt/autoprint/.is choice} +\pgfkeys{/PYTX/pkgopt/autoprint/true/.code=\booltrue{pytx@opt@autoprint}} +\pgfkeys{/PYTX/pkgopt/autoprint/false/.code=\boolfalse{pytx@opt@autoprint}} +\pgfkeys{/PYTX/pkgopt/autostdout/.default=true} +\pgfkeys{/PYTX/pkgopt/autostdout/.is choice} +\pgfkeys{/PYTX/pkgopt/autostdout/true/.code=\booltrue{pytx@opt@autoprint}} +\pgfkeys{/PYTX/pkgopt/autostdout/false/.code=\boolfalse{pytx@opt@autoprint}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\setpythontexautoprint}\begin{macro}{\setpythontexautostdout} +% Sometimes it may be useful to switch |autoprint| on and off within different parts of a document, rather than setting it to a single setting for the entire document. So we provide a command for that purpose. Note that the command overrides the package-level option. +% \begin{macrocode} +\newcommand{\setpythontexautoprint}[1]{% + \Depythontex{cmd:setpythontexautoprint:m:n}% + \ifstrequal{#1}{true}{\booltrue{pytx@opt@autoprint}}{}% + \ifstrequal{#1}{false}{\boolfalse{pytx@opt@autoprint}}{}% +} +\newcommand{\setpythontexautostdout}[1]{% + \Depythontex{cmd:setpythontexautostdout:m:n}% + \ifstrequal{#1}{true}{\booltrue{pytx@opt@autoprint}}{}% + \ifstrequal{#1}{false}{\boolfalse{pytx@opt@autoprint}}{}% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% \subsubsection{Debug} +% \begin{macro}{pytx@opt@stdout} +% This option determines whether printed content/content written to stdout is included in the document. Disabling the inclusion of printed content is useful when the printed content contains \LaTeX\ errors that would prevent successful compilation. +% \begin{macrocode} +\newbool{pytx@opt@stdout} +\booltrue{pytx@opt@stdout} +\pgfkeys{/PYTX/pkgopt/debug/.code=\boolfalse{pytx@opt@stdout}} +\pgfkeys{/PYTX/pkgopt/stdout/.default=true} +\pgfkeys{/PYTX/pkgopt/stdout/.is choice} +\pgfkeys{/PYTX/pkgopt/stdout/true/.code=\booltrue{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option stdout is deprecated; use option debug}} +\pgfkeys{/PYTX/pkgopt/stdout/false/.code=\boolfalse{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option stdout is deprecated; use option debug}} +\pgfkeys{/PYTX/pkgopt/print/.default=true} +\pgfkeys{/PYTX/pkgopt/print/.is choice} +\pgfkeys{/PYTX/pkgopt/print/true/.code=\booltrue{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option print is deprecated; use option debug}} +\pgfkeys{/PYTX/pkgopt/print/false/.code=\boolfalse{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option print is deprecated; use option debug}} +\AtBeginDocument{% + \ifbool{pytx@opt@stdout}{}{% + \PackageWarning{\pytx@packagename}{Using package option debug}% + }% +} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{makestderr} +% \begin{macro}{pytx@opt@stderr} +% The |makestderr| option determines whether stderr is saved and may be included in the document via |\stderrpythontex|. +% \begin{macrocode} +\newbool{pytx@opt@stderr} +\pgfkeys{/PYTX/pkgopt/makestderr/.default=true} +\pgfkeys{/PYTX/pkgopt/makestderr/.is choice} +\pgfkeys{/PYTX/pkgopt/makestderr/true/.code=\booltrue{pytx@opt@stderr}} +\pgfkeys{/PYTX/pkgopt/makestderr/false/.code=\boolfalse{pytx@opt@stderr}} +\pgfkeys{/PYTX/pkgopt/stderr/.default=true} +\pgfkeys{/PYTX/pkgopt/stderr/.is choice} +\pgfkeys{/PYTX/pkgopt/stderr/true/.code=\booltrue{pytx@opt@stderr}% + \PackageWarning{\pytx@packagename}{Option stderr is deprecated; use option makestderr}} +\pgfkeys{/PYTX/pkgopt/stderr/false/.code=\boolfalse{pytx@opt@stderr}% + \PackageWarning{\pytx@packagename}{Option stderr is deprecated; use option makestderr}} +% \end{macrocode} +% \end{macro} +% +% + + +% \subsubsection{stderrfilename} +% \begin{macro}{\pytx@opt@stderrfilename} +% This option determines how the file name appears in |stderr|. +% \begin{macrocode} +\def\pytx@opt@stderrfilename{full} +\pgfkeys{/PYTX/pkgopt/stderrfilename/.default=full} +\pgfkeys{/PYTX/pkgopt/stderrfilename/.is choice} +\pgfkeys{/PYTX/pkgopt/stderrfilename/full/.code=\def\pytx@opt@stderrfilename{full}} +\pgfkeys{/PYTX/pkgopt/stderrfilename/session/.code=\def\pytx@opt@stderrfilename{session}} +\pgfkeys{/PYTX/pkgopt/stderrfilename/genericfile/.code=% + \def\pytx@opt@stderrfilename{genericfile}} +\pgfkeys{/PYTX/pkgopt/stderrfilename/genericscript/.code=% + \def\pytx@opt@stderrfilename{genericscript}} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Python's \texttt{\_\_future\_\_} module} +% \begin{macro}{\pytx@opt@pyfuture} +% The |pyfuture| option determines what is imported from the |__future__| module under Python 2. It has no effect under Python 3. +% \begin{macrocode} +\def\pytx@opt@pyfuture{default} +\pgfkeys{/PYTX/pkgopt/pyfuture/.is choice} +\pgfkeys{/PYTX/pkgopt/pyfuture/default/.code=\def\pytx@opt@pyfuture{default}} +\pgfkeys{/PYTX/pkgopt/pyfuture/all/.code=\def\pytx@opt@pyfuture{all}} +\pgfkeys{/PYTX/pkgopt/pyfuture/none/.code=\def\pytx@opt@pyfuture{none}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@opt@pyconfuture} +% The |pyconfuture| option determines what is automatically imported from the |__future__| module under Python 2, for |console| content. It has no effect under Python 3. +% \begin{macrocode} +\def\pytx@opt@pyconfuture{none} +\pgfkeys{/PYTX/pkgopt/pyconfuture/.is choice} +\pgfkeys{/PYTX/pkgopt/pyconfuture/default/.code=\def\pytx@opt@pyconfuture{default}} +\pgfkeys{/PYTX/pkgopt/pyconfuture/all/.code=\def\pytx@opt@pyconfuture{all}} +\pgfkeys{/PYTX/pkgopt/pyconfuture/none/.code=\def\pytx@opt@pyconfuture{none}} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Upquote} +% \begin{macro}{pytx@opt@upquote} +% The |upquote| option determines whether the |upquote| package is loaded. It makes quotes within verbatim contexts \expandafter|\textquotesingle| rather than |'|. This is important, because it means that code may be copied directly from the compiled PDF and executed without any errors due to quotes |'| being copied as acute accents \texttt{\'}. +% \begin{macrocode} +\newbool{pytx@opt@upquote} +\booltrue{pytx@opt@upquote} +\pgfkeys{/PYTX/pkgopt/upquote/.default=true} +\pgfkeys{/PYTX/pkgopt/upquote/.is choice} +\pgfkeys{/PYTX/pkgopt/upquote/true/.code=\booltrue{pytx@opt@upquote}} +\pgfkeys{/PYTX/pkgopt/upquote/false/.code=\boolfalse{pytx@opt@upquote}} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Fix math spacing} +% \begin{macro}{pytx@opt@fixlr} +% The |fixlr| option fixes extra, undesirable spacing in mathematical formulae introduced by the commands |\left| and |\right|. For example, compare the results of |$\sin(x)$| and |$\sin\left(x\right)$|: $\sin(x)$ and $\sin\left(x\right)$. The |fixlr| option fixes this, using a solution proposed by Mateus Ara\'{u}jo, Philipp Stephani, and Heiko Oberdiek.\footnote{ \url{http://tex.stackexchange.com/questions/2607/spacing-around-left-and-right}} +% \begin{macrocode} +\newbool{pytx@opt@fixlr} +\pgfkeys{/PYTX/pkgopt/fixlr/.default=true} +\pgfkeys{/PYTX/pkgopt/fixlr/.is choice} +\pgfkeys{/PYTX/pkgopt/fixlr/true/.code=\booltrue{pytx@opt@fixlr}} +\pgfkeys{/PYTX/pkgopt/fixlr/false/.code=\boolfalse{pytx@opt@fixlr}} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Keep temporary files} +% \begin{macro}{\pytx@opt@keeptemps} +% By default, \pytex\ tries to be very tidy. It creates many temporary files, but deletes all that are not required to compile the document, keeping the overall file count very low. At times, particularly during debugging, it may be useful to keep these temporary files, so that code, errors, and output may be examined more directly. The |keeptemps| option makes this possible. +% \begin{macrocode} +\def\pytx@opt@keeptemps{none} +\pgfkeys{/PYTX/pkgopt/keeptemps/.default=all} +\pgfkeys{/PYTX/pkgopt/keeptemps/.is choice} +\pgfkeys{/PYTX/pkgopt/keeptemps/all/.code=\def\pytx@opt@keeptemps{all}} +\pgfkeys{/PYTX/pkgopt/keeptemps/code/.code=\def\pytx@opt@keeptemps{code}} +\pgfkeys{/PYTX/pkgopt/keeptemps/none/.code=\def\pytx@opt@keeptemps{none}} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Pygments} +% \begin{macro}{pytx@opt@pygments} +% By default, \pytex\ uses |fancyvrb| to typeset code. This provides nice formatting and font options, but no syntax highlighting. The |prettyprinter| options, and |pygments| alias, determine whether Pygments or |fancyvrb| is used to typeset code. Pygments is a generic syntax highlighter written in Python. Since \pytex\ sends code to Python anyway, having Pygments process the code is only a small additional step and in many cases takes little if any extra time to execute.\footnote{Pygments code highlighting is executed as a separate process by |pythontex.py|, so it runs in parallel on a multicore system. Pygments usage is optimized by saving highlighted code and only reprocessing it when changed.} +% +% Command and environment families obey the |prettyprinter| option by default, but they may be set to override it and always use Pygments or always use |fancyvrb|, via |\setpythontexprettyprinter| and |\setpygmentsprettyprinter|. +% \begin{macrocode} +\newbool{pytx@opt@pygments} +\booltrue{pytx@opt@pygments} +\pgfkeys{/PYTX/pkgopt/prettyprinter/.is choice} +\pgfkeys{/PYTX/pkgopt/prettyprinter/pygments/.code=\booltrue{pytx@opt@pygments}} +\pgfkeys{/PYTX/pkgopt/prettyprinter/fancyvrb/.code=\boolfalse{pytx@opt@pygments}} +\pgfkeys{/PYTX/pkgopt/pygments/.default=true} +\pgfkeys{/PYTX/pkgopt/pygments/.is choice} +\pgfkeys{/PYTX/pkgopt/pygments/true/.code=\booltrue{pytx@opt@pygments}} +\pgfkeys{/PYTX/pkgopt/pygments/false/.code=\boolfalse{pytx@opt@pygments}} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{pytx@opt@pyginline} +% This option governs whether, when Pygments is in use, it highlights inline content. +% \begin{macrocode} +\newbool{pytx@opt@pyginline} +\booltrue{pytx@opt@pyginline} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/.default=true} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/.is choice} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/true/.code=\booltrue{pytx@opt@pyginline}} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/false/.code=\boolfalse{pytx@opt@pyginline}} +\pgfkeys{/PYTX/pkgopt/pyginline/.default=true} +\pgfkeys{/PYTX/pkgopt/pyginline/.is choice} +\pgfkeys{/PYTX/pkgopt/pyginline/true/.code=\booltrue{pytx@opt@pyginline}} +\pgfkeys{/PYTX/pkgopt/pyginline/false/.code=\boolfalse{pytx@opt@pyginline}} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{pytx@pyglexer} +% For completeness, we need a way to set the Pygments lexer for all content. Note that in general, resetting the lexers for all content is not desirable. +% \begin{macrocode} +\def\pytx@pyglexer{} +\pgfkeys{/PYTX/pkgopt/pyglexer/.code=\def\pytx@pyglexer{#1}} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@pygopt} +% We also need a way to specify Pygments options at the package level. This is accomplished via the |pygopt| option: |pygopt|=\{\meta{options}\}. Note that the options must be enclosed in curly braces since they contain equals signs and thus must be distinguishable from package options. +% +% Currently, three options may be passed in this manner: |style=|\meta{style}, which sets the formatting style; |texcomments|, which allows \LaTeX\ in code comments to be rendered; and |mathescape|, which allows \LaTeX\ math mode (|$...$|) in comments. The |texcomments| and |mathescape| options may be used with a boolean argument; if an argument is not supplied, true is assumed. As an example of |pygopt| usage, consider the following: +% \begin{verbatim} +% pygopt={style=colorful, texcomments=True, mathescape=False} +% \end{verbatim} +% The usage of capitalized |True| and |False| is more pythonic, but is not strictly require. +% +% While the package-level |pygments| option may be overridden by individual commands and environments (though it is not by default), the package-level Pygments options cannot be overridden by individual commands and environments. +% \begin{macrocode} +\def\pytx@pygopt{} +\pgfkeys{/PYTX/pkgopt/pygopt/.code=\def\pytx@pygopt{#1}} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@fvextfile} +% By default, code highlighted by Pygments, the |console| environment, and some other content is brought back via |fancyvrb|'s |SaveVerbatim| macro, which saves verbatim content into a macro and then allows it to be restored. This makes it possible for all Pygments content to be brought back in a single file, keeping the total file count low (which is a major priority for \pytex!). This approach does have a disadvantage, though, because |SaveVerbatim| slows down as the length of saved code increases.\footnote{The macro in which code is saved is created by grabbing the code one line at a time, and for each line redefining the macro to be its old value with the additional line tacked on. This is rather inefficient, but apparently there isn't a good alternative.} To deal with this issue, we create the |fvextfile| option. This option takes an integer, |fvextfile|=\meta{integer}. All content that is more than \meta{integer} lines long will be saved to its own external file and inputted from there, rather than saved and restored via |SaveVerbatim| and |UseVerbatim|. This provides a workaround should speed ever become a hindrance for large blocks of code. +% +% A default value of 25 is set. There is nothing special about 25; it is just a relatively reasonably cutoff. If the option is unused, it has a value of $-1$, which is converted to the maximum integer on the Python side. +% \begin{macrocode} +\def\pytx@fvextfile{-1} +\pgfkeys{/PYTX/pkgopt/fvextfile/.default=25} +\pgfkeys{/PYTX/pkgopt/fvextfile/.code=\IfInteger{#1}{% + \ifnum#1>0\relax + \def\pytx@fvextfile{#1}% + \else + \PackageError{\pytx@packagename}{option fvextfile must be an integer > 0}{}% + \fi}% + {\PackageError{\pytx@packagename}{option fvextfile must be an integer > 0}{}}% +} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Python console environment} +% \begin{macro}{\pytx@opt@pyconbanner} +% This option governs the appearance (or disappearance) of a banner at the beginning of Python console environments. The options |none| (no banner), |standard| (standard Python banner), |default| (default banner for Python's |code| module, standard banner plus interactive console class name), and |pyversion| (banner in the form |Python x.y.z|) are accepted. +% \begin{macrocode} +\def\pytx@opt@pyconbanner{none} +\pgfkeys{/PYTX/pkgopt/pyconbanner/.is choice} +\pgfkeys{/PYTX/pkgopt/pyconbanner/none/.code=\def\pytx@opt@pyconbanner{none}} +\pgfkeys{/PYTX/pkgopt/pyconbanner/standard/.code=\def\pytx@opt@pyconbanner{standard}} +\pgfkeys{/PYTX/pkgopt/pyconbanner/default/.code=\def\pytx@opt@pyconbanner{default}} +\pgfkeys{/PYTX/pkgopt/pyconbanner/pyversion/.code=\def\pytx@opt@pyconbanner{pyversion}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@opt@pyconfilename} +% This option governs the file name that appears in error messages in the console. The file name may be either |stdin|, as it is in a standard interactive interpreter, or |console|, as it would typically be for the Python |code| module. +% \begin{verbatim} +% Traceback (most recent call last): +% File "<file name>", line <line no>, in <module> +% \end{verbatim} +% \begin{macrocode} +\def\pytx@opt@pyconfilename{stdin} +\pgfkeys{/PYTX/pkgopt/pyconfilename/.is choice} +\pgfkeys{/PYTX/pkgopt/pyconfilename/stdin/.code=\def\pytx@opt@pyconfilename{stdin}} +\pgfkeys{/PYTX/pkgopt/pyconfilename/console/.code=\def\pytx@opt@pyconfilename{console}} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{depythontex} +% \begin{macro}{pytx@opt@depythontex} +% This option governs whether \pytex\ saved data that can be used to create a version of the .tex file that does not require \pytex\ to be compiled. This option should be useful for converting a \pytex\ document into a more standard \TeX\ document when sharing or publishing documents. +% +% While we're at it, we go ahead and define dummy versions of the |depythontex| macros, so that they can be used in defining commands that are used within the package, not just outside of it. +% \begin{macrocode} +\newbool{pytx@opt@depythontex} +\pgfkeys{/PYTX/pkgopt/depythontex/.default=true} +\pgfkeys{/PYTX/pkgopt/depythontex/.is choice} +\pgfkeys{/PYTX/pkgopt/depythontex/true/.code=\booltrue{pytx@opt@depythontex}} +\pgfkeys{/PYTX/pkgopt/depythontex/false/.code=\boolfalse{pytx@opt@depythontex}} +\let\Depythontex\@gobble +\let\DepyFile\@gobble +\let\DepyMacro\@gobble +\let\DepyListing\@empty +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Process options} +% Now we process the package options. +% \begin{macrocode} +\ProcessPgfPackageOptions{/PYTX/pkgopt} +% \end{macrocode} +% +% The |fixlr| option only affects one thing, so we go ahead and take care of that. Notice that before we patch |\left| and |\right|, we make sure that they have not already been patched by checking how |\left| is expanded. This is important if the user has manually patched these commands, is using the |mleftright| package, or accidentally loads \pytex\ twice. +% \begin{macrocode} +\ifbool{pytx@opt@fixlr}{ + \IfStrEq{\detokenize\expandafter{\left}}{\detokenize{\left}}{ + \let\originalleft\left + \let\originalright\right + \renewcommand{\left}{\mathopen{}\mathclose\bgroup\originalleft} + \renewcommand{\right}{\aftergroup\egroup\originalright} + }{} +}{} +% \end{macrocode} +% +% Likewise, the |upquote| option. +% \begin{macrocode} +\ifbool{pytx@opt@upquote}{\RequirePackage{upquote}}{} +% \end{macrocode} +% +% If the |depythontex| option is used, we also need to disable Pygments highlighting. This is necessary because some content, such as |console| environments, is needed in a non-highlighted form, so that it will not contain any special macros. +% \begin{macrocode} +\ifbool{pytx@opt@depythontex}{\boolfalse{pytx@opt@pygments}}{} +% \end{macrocode} +% +% \subsection{Utility macros and input/output setup} +% Once options are processed, we proceed to define a number of utility macros and setup the file input/output that is required by \pytex. We also create macros and perform setup needed by depythontex, since these are closely related to input/output. +% +% \subsubsection{Automatic counter creation} +% +% \begin{macro}{\pytx@CheckCounter} +% We will be using counters to give each command/environment a unique identifier, as well as to manage line numbering of code when desired. We don't know the names of the counters ahead of time (this is actually determined by the user's naming of code sessions), so we need a macro that checks whether a counter exists, and if not, creates it. +% \begin{macrocode} +\def\pytx@CheckCounter#1{% + \ifcsname c@#1\endcsname\else\newcounter{#1}\fi +} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Saving verbatim content in macros} +% +% \begin{macro}{\pytx@SVMCR} +% Commands like |\py| bring in string representations of objects. Printed content is saved to external files, but commands like |\py| bring in content by saving it in macros. A single large file of macro definitions is brought in, rather than many external files. +% +% This prevents the creation of unnecessary files, but it also has a significant drawback: only some content can be saved in a standard macro. In particular, verbatim content using |\verb| and |verbatim| will not work. So we need a way to save anything in a macro. The solution is to create a special macro that captures its argument verbatim. The argument is then tokenized when it is used via |\scantokens|. All of this requires a certain amount of catcode trickery. +% +% While this approach works with the XeTeX and pdfTeX engines, it does not work with the LuaTeX engine, which has a known bug in its implementation of |\scantokens|.\footnote{\url{http://tracker.luatex.org/view.php?id=733}} So we provide a separate version for Lua\TeX\ that does not support verbatim. The space after the |#1| is intentional, so that the newline at the beginning of the macro definition is gobbled, and the macro content will start with text rather than leading whitespace. +% +% \begin{macrocode} +\def\pytx@SVMCR#1{% + \edef\pytx@tmp{\csname #1\endcsname}% + \begingroup + \endlinechar`\^^J + \let\do\@makeother\dospecials + \pytx@SVMCR@i} +\begingroup +\catcode`!=0 +!catcode`!\=12 +!long!gdef!pytx@SVMCR@i#1\endpytx@SVMCR^^J{% + !endgroup + !expandafter!gdef!pytx@tmp{% + !expandafter!scantokens!expandafter{#1!empty}}% +}% +!endgroup +\expandafter\ifx\csname directlua\endcsname\relax\else +\def\pytx@SVMCR#1 {% + \edef\pytx@tmp{\csname #1\endcsname}% + \pytx@SVMCR@i} +\long\def\pytx@SVMCR@i#1\endpytx@SVMCR{% + \expandafter\gdef\pytx@tmp{#1}% +} +\fi +% \end{macrocode}\end{macro} +% +% +% \subsubsection{Code context} +% \begin{macro}{\pytx@context}\begin{macro}{\pytx@SetContext}\begin{macro}{\definepythontexcontext} +% It would be nice if when our code is executed, we could know something about its context, such as the style of its surroundings or information about page size. +% +% By default, no contextual information is passed to \LaTeX. There is a wide variety of information that could be passed, but most use cases would only need a very specific subset. Instead, the user can customize what information is passed to \LaTeX. The |\definepythontexcontext| macro defines what is passed. It creates the |\pytx@SetContext| macro, which creates |\pytx@context|, in which the expanded context information is stored. The context should only be defined in the preamble, so that it is consistent throughout the document. +% +% If you are interested in typesetting mathematics based on math styles, you should use the |\mathchoice| macro rather than attempting to pass contextual information. +% \begin{macrocode} +\newcommand{\definepythontexcontext}[1]{% + \def\pytx@SetContext{% + \edef\pytx@context{#1}% + }% +} +\definepythontexcontext{} +\@onlypreamble\definepythontexcontext +% \end{macrocode} +% \end{macro}\end{macro}\end{macro} +% +% \subsubsection{Code groups} +% By default, \pytex\ executes code based on sessions. All of the code entered within a command and environment family is divided based on sessions, and each session is saved to a single external file and executed. If you have a calculation that will take a while, you can simply give it its own named session, and then the code will only be executed when there is a change within that session. +% +% While this approach is appropriate for many scenarios, it is sometimes inefficient. For example, suppose you are writing a document with multiple chapters, and each chapter needs its own session. You could manually do this, but that would involve a lot of commands like |\py[chapter x]{|\meta{some~code}|}|, which means lots of extra typing and extra session names. So we need a way to subdivide or restart sessions, based on context such as chapter, section, or subsection. +% +% ``Groups'' provide a solution to this problem. Each session is subdivided based on groups behind the scenes. By default, this changes nothing, because each session is put into a single default group. But the user can redefine groups based on chapter, section, and other counters, so that sessions are automatically subdivided accordingly. Note that there is no continuity between sessions thus subdivided. For example, if you set groups to change between chapters, there will be no continuity between the code of those chapters, even if all the code is within the same named session. If you require continuity, the groups approach is probably not appropriate. You could consider saving results at the end of one chapter and loading them at the beginning of the next, but that introduces additional issues in keeping all code properly synchronized, since code is executed only when it changes, not when any data it loads may have changed. +% +% +% \begin{macro}{\restartpythontexsession} +% \begin{macro}{\pytx@group} +% \begin{macro}{\pytx@SetGroup} +% \begin{macro}{\pytx@SetGroupVerb} +% \begin{macro}{\pytx@SetGroupCons} +% We begin by creating the |\restartpythontexsession| macro. It creates the |\pytx@SetGroup*| macros, which create |\pytx@group|, in which the expanded context information is stored. The context should only be defined in the preamble, so that it is consistent throughout the document. Note that groups should be defined so that they will only contain characters that are valid in file names, because groups are used in naming temporary files. It is also a good idea to avoid using periods, since \LaTeX\ input of file names containing multiple periods can sometimes be tricky. For best results, use A-Z, a-z, 0-9, and the hyphen and underscore characters to define groups. If groups contain numbers from multiple sources (for example, chapter and section), the numbers should be separated by a non-numeric character to prevent unexpected collisions (for example, distinguishing chapter 1-11 from 11-1). For example, |\restartpythontexsession{\arabic{chapter}-\arabic{section}}| could be a good approach. +% +% Three forms of |\pytx@SetGroup*| are provided. |\pytx@SetGroup| is for general code use. |\pytx@SetGroupVerb| is for use in verbatim contexts. It splits verbatim content off into its own group. That way, verbatim content does not affect the instance numbers of code that is actually executed. This prevents code from needing to be run every time verbatim content is added or removed; code is only executed when it is actually changed. |pytx@SetGroupCons| is for |console| environments. It separate console content from executed code and from verbatim content, again for efficiency reasons. +% \begin{macrocode} +\newcommand{\restartpythontexsession}[1]{% + \Depythontex{cmd:restartpythontexsession:m:n}% + \def\pytx@SetGroup{% + \edef\pytx@group{#1}% + }% + \def\pytx@SetGroupVerb{% + \edef\pytx@group{#1verb}% + }% + \def\pytx@SetGroupCons{% + \edef\pytx@group{#1cons}% + }% + \AtBeginDocument{% + \pytx@SetGroup + \IfSubStr{\pytx@group}{verb}{% + \PackageError{\pytx@packagename}% + {String "verb" is not allowed in \string\restartpythontexsession}% + {Use \string\restartpythontexsession with a valid argument}}{}% + \IfSubStr{\pytx@group}{cons}{% + \PackageError{\pytx@packagename}% + {String "cons" is not allowed in \string\restartpythontexsession}% + {Use \string\restartpythontexsession with a valid argument}}{}% + }% +} +% \end{macrocode} +% \end{macro}\end{macro}\end{macro}\end{macro}\end{macro} +% +% For the sake of consistency, we only allow group behaviour to be set in the preamble. And if the group is not set by the user, then we use a single default group for each session. +% \begin{macrocode} +\@onlypreamble\restartpythontexsession +\restartpythontexsession{default} +% \end{macrocode} +% +% \subsubsection{File input and output} +% +% \begin{macro}{\pytx@jobname} +% We will need to create directories and files for \pytex\ output, and some of these will need to be named using |\jobname|. This presents a problem. Ideally, the user will choose a job name that does not contain spaces. But if the job name does contain spaces, then we may have problems bringing in content from a directory or file that is named based on the job, due to the space characters. So we need a ``sanitized'' version of |\jobname|. We replace spaces with hyphens. We replace double quotes |"| with nothing. Double quotes are placed around job names containing spaces by \TeX\ Live, and thus may be the first and last characters of |\jobname|. Since we are replacing any spaces with hyphens, quote delimiting is no longer needed, and in any case, some operating systems (Windows) balk at creating directories or files with names containing double quotes. We also replace asterisks with hyphens, since MiK\TeX\ (at least v.\ 2.9) apparently replaces spaces with asterisks in |\jobname|,\footnote{\href{http://tex.stackexchange.com/questions/14949/why-does-jobname-give-s-instead-of-spaces-and-how-do-i-fix-this}{http://tex.stackexchange.com/questions/14949/why-does-jobname-give-s-instead-of-spaces-and-how-do-i-fix-this}} and some operating systems may not be happy with names containing asterisks. +% +% This approach to ``sanitizing'' |\jobname| is not foolproof. If there are ever two files in a directory that both use \pytex, and if their names only differ by these substitutions for spaces, quotes, and asterisks, then the output of the two files will collide. We believe that it is better to graciously handle the possibility of space characters at the expense of nearly identical file names, since nearly identical file names are arguably a much worse practice than file names containing spaces, and since such nearly identical file names should be much rarer. At the same time, in rare cases a collision might occur, and in even rarer cases it might go unnoticed.\footnote{In general, a collision would produce errors, and the user would thereby become aware of the collision. The dangerous case is when the two files with similar names use exactly the same \pytex\ commands, the same number of times, so that the naming of the output is identical. In that case, no errors would be issued.} To prevent such issues, |pythontex.py| checks for collisions and issues a warning if a potential collision is detected. +% \begin{macrocode} +\StrSubstitute{\jobname}{ }{-}[\pytx@jobname] +\StrSubstitute{\pytx@jobname}{"}{}[\pytx@jobname] +\StrSubstitute{\pytx@jobname}{*}{-}[\pytx@jobname] +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@outputdir}\begin{macro}{\setpythontexoutputdir} +% To keep things tidy, all \pytex\ files are stored in a directory that is created in the document root directory. By default, this directory is called |pythontex-files-|\meta{sanitized~jobname}, but we want to provide the user with the option to customize this. For example, when \meta{sanitized~jobname} is very long, it might be convenient to use |pythontex-|\meta{abbreviated~name}. +% +% The command |\setpythontexoutputdir| stores the name of \pytex's output directory in |\pytx@outputdir|. If the |graphicx| package is loaded, the output directory is also added to the graphics path at the beginning of the document, so that files in the output directory may be included within the main document without the necessity of specifying path information. The command |\setpythontexoutputdir| is only allowed in the preamble, because the location of \pytex\ content should be specified before the body of the document is typeset. +% \begin{macrocode} +\newcommand{\setpythontexoutputdir}[1]{% + \Depythontex{cmd:setpythontexoutputdir:m:n}% + \def\pytx@outputdir{#1}} +\setpythontexoutputdir{pythontex-files-\pytx@jobname} +\AtBeginDocument{% + \@ifpackageloaded{graphicx}{% + \ifx\Ginput@path\@undefined + \graphicspath{{\pytx@outputdir/}}% + \else + \g@addto@macro\Ginput@path{{\pytx@outputdir/}}% + \fi + }{}% +} +\@onlypreamble\setpythontexoutputdir +% \end{macrocode} +% +% +% \begin{macro}{pytx@workingdir}\begin{macro}{\setpythontexworkingdir} +% We need to be able to set the current working directory for the scripts executed by \pytex. By default, the working directory should be the same as the output directory. That way, any files saved in the current working directory will be in the \pytex\ output directory, and will thus be kept separate. But in some cases the user may wish to specify a different working directory, such as the document root. +% +% \begin{macrocode} +\newcommand{\setpythontexworkingdir}[1]{% + \Depythontex{cmd:setpythontexworkingdir:m:n}% + \def\pytx@workingdir{#1}% +} +\@onlypreamble\setpythontexworkingdir +\AtBeginDocument{% + \ifcsname pytx@workingdir\endcsname\else + %\setpythontexworkingdir{\pytx@outputdir}\fi Depythontex + \let\pytx@workingdir\pytx@outputdir\fi +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{pytx@usedpygments} +% Once we have specified the output directory, we are free to pull in content from it. Most content from the output directory will be pulled in manually by the user (for example, via |\includegraphics|) or automatically by \pytex\ as it goes along. But content ``printed'' by code commands and environments (via macros) as well as code typeset by Pygments needs to be included conditionally, based on whether it exists and on user preferences. +% +% This gets a little tricky. We only want to pull in the Pygments content if it is actually used, since Pygments content will typically use |fancyvrb|'s |SaveVerb| environment, and this can slow down compilation when very large chunks of code are saved. It doesn't matter if the code is actually used; saving it in a macro is what potentially slows things down. So we create a bool to keep track of whether Pygments is ever actually used, and only bring in Pygments content if it is.\footnote{The same effect could be achieved by having |pythontex.py| delete the Pygments content whenever it is run and Pygments is not used. But that approach is faulty in two regards. First, it requires that |pythontex.py| be run, which is not necessarily the case if the user simply sets the package option |pygments| to |false| and the recompiles. Second, even if it could be guaranteed that the content would be deleted, such an approach would not be optimal. It is quite possible that the user wishes to temporarily turn off Pygments usage to speed compilation while working on other parts of the document. In this case, deleting the Pygments content is simply deleting data that must be recreated when Pygments is turned back on.} This bool must be set to |true| whenever a command or environment is created that makes use of Pygments (in practice, we will simply set it to true when a family is created). Note that we cannot use the |pytx@opt@pygments| bool for this purpose, because it only tells us if the package option for Pygments usage is |true| or |false|. Typically, this will determine if any Pygments content is used. But it is possible for the user to create a command and environment family that overrides the package option (indeed, this may sometimes be desirable, for example, if the user wishes code in a particular language never to be highlighted). Thus, a new bool is needed to allow detection in such nonstandard cases. +% \begin{macrocode} +\newbool{pytx@usedpygments} +% \end{macrocode} +% \end{macro} +% +% Now we can conditionally bring in the Pygments content. Note that we must use the |etoolbox| macro |\AfterEndPreamble|. This is because commands and environments are created using |\AtBeginDocument|, so that the user can change their properties in the preamble before they are created. And since the commands and environments must be created before we know the final state of |pytx@usedpygments|, we must bring in Pygments content after that. +% \begin{macrocode} +\AfterEndPreamble{% + \ifbool{pytx@usedpygments}% + {\InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxpyg}{}{}}{}% +} +% \end{macrocode} +% +% % While we are pulling in content, we also pull in the file of macros that stores some inline ``printed'' content, if the file exists. Since we need this file in general, and since it will not typically invole a noticeable speed penalty, we bring it in at the beginning of the document without any special conditions. +% \begin{macrocode} +\AtBeginDocument{% + \makeatletter + \InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxmcr}{}{}% + \makeatother +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@codefile} +% We create a new write, named |\pytx@codefile|, to which we will save code. All the code from the document will be written to this single file, interspersed with information specifying where in the document it came from. \pytex\ parses this file to separate the code into individual sessions and groups. These are then executed, and the identifying information is used to tie code output back to the original code in the document.\footnote{The choice to write all code to a single file is the result of two factors. First, \TeX\ has a limited number of output registers available (16), so having a separate output stream for each group or session is not possible. The |morewrites| package from Bruno Le Floch potentially removes this obstacle, but since this package is very recent (README from 2011/7/10), we will not consider using additional writes in the immediate future. Second, one of the design goals of \pytex\ is to minimize the number of persistent files created by a run. This keeps directories cleaner and makes file synchronization/transfer somewhat simpler. Using one write per session or group could result in numerous code files, and these could only be cleaned up by |pythontex.py| since \LaTeX\ cannot delete files itself (well, without unrestricted |write18|). Using a single output file for code does introduce a speed penalty since the code does not come pre-sorted by session or group, but in typical usage this should be minimal. Adding an option for single or multiple code files may be something to reconsider at a later date.} +% \begin{macrocode} +\newwrite\pytx@codefile +\immediate\openout\pytx@codefile=\jobname.pytxcode +% \end{macrocode} +% \end{macro} +% +% In the code file, information from \pytex\ must be interspersed with the code. Some type of delimiting is needed for \pytex\ information. All \pytex\ content is written to the file in the form |=>PYTHONTEX#|\meta{content}|#|. When this content involves package options, the delimiter is modified to the form |=>PYTHONTEX:SETTINGS#|\meta{content}|#|. The |#| symbol is also used as a subdelimiter within \meta{content}. The |#| symbol is convenient as a delimiter since it has a special meaning in \TeX\ and is very unlikely to be accidentally entered by the user in unexpected locations without producing errors. Note that the usage of ``|=>PYTHONTEX#|'' as a beginning delimiter for \pytex\ data means that this string should \textbf{never} be written by the user at the beginning of a line, because |pythontex.py| will try to intepret it as data and will fail. +% +% \begin{macro}{\pytx@delimchar} +% We create a macro to store the delimiting character. +% \begin{macrocode} +\edef\pytx@delimchar{\string#} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@delim} +% We create a macro to store the starting delimiter. +% \begin{macrocode} +\edef\pytx@delim{=\string>PYTHONTEX\string#} +% \end{macrocode} +% \end{macro} +% \begin{macro}{\pytx@delimsettings} +% And we create a second macro to store the starting delimiter for settings that are passed to Python. +% \begin{macrocode} +\edef\pytx@delimsettings{=\string>PYTHONTEX:SETTINGS\string#} +% \end{macrocode} +% \end{macro} +% Settings need to be written to the code file. Some of these settings are not final until the beginning of the document, since they may be modified by the user within the preamble. Thus, all settings should be written at the end of the document, so that they will all be together and will not be interspersed with any code that was entered in the preamble. The order in which the settings are written is not significant, but for symmetry it should mirror the order in which they were defined. +% \begin{macrocode} +\AtEndDocument{% + \immediate\write\pytx@codefile{\pytx@delimsettings}% + \immediate\write\pytx@codefile{version=\pytx@packageversion}% + \immediate\write\pytx@codefile{outputdir=\pytx@outputdir}% + \immediate\write\pytx@codefile{workingdir=\pytx@workingdir}% + \immediate\write\pytx@codefile{gobble=\pytx@opt@gobble}% + \immediate\write\pytx@codefile{rerun=\pytx@opt@rerun}% + \immediate\write\pytx@codefile{hashdependencies=\pytx@opt@hashdependencies}% + \immediate\write\pytx@codefile{makestderr=\ifbool{pytx@opt@stderr}{true}{false}}% + \immediate\write\pytx@codefile{stderrfilename=\pytx@opt@stderrfilename}% + \immediate\write\pytx@codefile{keeptemps=\pytx@opt@keeptemps}% + \immediate\write\pytx@codefile{pyfuture=\pytx@opt@pyfuture}% + \immediate\write\pytx@codefile{pyconfuture=\pytx@opt@pyconfuture}% + \immediate\write\pytx@codefile{pygments=\ifbool{pytx@opt@pygments}{true}{false}}% + \immediate\write\pytx@codefile{pygglobal=:GLOBAL|\pytx@pyglexer|\pytx@pygopt}% + \immediate\write\pytx@codefile{fvextfile=\pytx@fvextfile}% + \immediate\write\pytx@codefile{pyconbanner=\pytx@opt@pyconbanner}% + \immediate\write\pytx@codefile{pyconfilename=\pytx@opt@pyconfilename}% + \immediate\write\pytx@codefile{depythontex=\ifbool{pytx@opt@depythontex}{true}{false}}% +} +% \end{macrocode} +% +% \begin{macro}{\pytx@WriteCodefileInfo}\begin{macro}{\pytx@WriteCodefileInfoExt} +% Later, we will frequently need to write \pytex\ information to the code file in standardized form. We create a macro to simplify that process. We also create an alternate form, for use with external files that must be inputted or read in by \pytex\ and processed. While the standard form employs a counter that is incremented elsewhere, the version for external files substitutes a zero (0) for the counter, because each external file must be unique in name and thus numbering via a counter is redundant.\footnote{The external-file form also takes an optional argument. This corresponds to a command-line argument that is passed to an external file during the file's execution. Currently, executing external files, with or without arguments, is not implemented. But this feature is under consideration, and the macro retains the optional argument for the potential future compatibility.} +% \begin{macrocode} +\def\pytx@argsrun{} +\def\pytx@argspprint{} +\def\pytx@WriteCodefileInfo{% + \ifcurrfile{\currfilebase}{\jobname}% + {\let\pytx@currfile\@empty}{\let\pytx@currfile\currfilename}% + \immediate\write\pytx@codefile{\pytx@delim\pytx@type\pytx@delimchar% + \pytx@session\pytx@delimchar\pytx@group\pytx@delimchar% + \arabic{\pytx@counter}\pytx@delimchar\pytx@cmd\pytx@delimchar% + \pytx@context\pytx@delimchar\pytx@argsrun\pytx@delimchar% + \pytx@argspprint\pytx@delimchar% + \pytx@currfile\pytx@delimchar% + \the\inputlineno\pytx@delimchar}% +} +\newcommand{\pytx@WriteCodefileInfoExt}[1][]{% + \ifcurrfile{\currfilebase}{\jobname}% + {\let\pytx@currfile\@empty}{\let\pytx@currfile\currfilename}% + \immediate\write\pytx@codefile{\pytx@delim\pytx@type\pytx@delimchar% + \pytx@session\pytx@delimchar\pytx@group\pytx@delimchar% + 0\pytx@delimchar\pytx@cmd\pytx@delimchar% + \pytx@context\pytx@delimchar\pytx@argsrun\pytx@delimchar% + \pytx@argspprint\pytx@delimchar% + \pytx@currfile\pytx@delimchar% + \the\inputlineno\pytx@delimchar#1}% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \subsubsection{Interface to \texttt{fancyvrb}} +% The |fancyvrb| package is used to typeset lines of code, and its internals are also used to format inline code snippets. We need a way for each family of \pytex\ commands and environments to have its own independent |fancyvrb| settings. +% \begin{macro}{\pytx@fvsettings}\begin{macro}{\setpythontexfv} +% The macro |\setpythontexfv|\oarg{family}\marg{settings} takes \meta{settings} and stores them in a macro that is run through |fancyvrb|'s |\fvset| at the beginning of \pytex\ code. If a \meta{family} is specified, the settings are stored in |\pytx@fvsettings@|\meta{family}, and the settings only apply to typeset code belonging to that family. If no optional argument is given, then the settings are stored in |\pytx@fvsettings|, and the settings apply to all typeset code. +% +% In the current implementation, |\setpythontexfv| and |\fvset| differ because the former is not persistent in the same sense as the latter. If we use |\fvset| to set one property, and then use it later to set another property, the setting for the original property is persistent. It remains until another |\fvset| command is issued to change it. In contrast, every time |\setpythontexfv| is used, it clears all prior settings and only the current settings actually apply. This is because |\fvset| stores the state of each setting in its own macro, while |\setpythontexfv| simply stores a string of settings that is passed to |\fvset| at the appropriate times. For typical use scenarios, this distinction shouldn't be important---usually, we will want to set the behavior of |fancyvrb| for all \pytex\ content, or for a family of \pytex\ content, and leave those settings constant throughout the document. Furthermore, environments that typeset code take |fancyvrb| commands as their second optional argument, so there is already a mechanism in place for changing the settings for a single environment. However, if we ever want to change the typesetting of code for only a small portion of a document (larger than a single environment), this persistence distinction does become important.\footnote{An argument could be made for having |\textbackslash setpythontexfv| behave exactly like |\textbackslash fvset|. Properly implementing this behavior would be tricky, because of inheritance issues between \pytex-wide and family-specific settings (this is probably a job for |pgfkeys|). Full persistence would likely require a large number of macros and conditionals. At least from the perspective of keeping the code clean and concise, the current approach is superior, and probably introduces minor annoyances at worst.} +% \begin{macrocode} +\newcommand{\setpythontexfv}[2][]{% + \Depythontex{cmd:setpythontexfv:om:n}% + \ifstrempty{#1}% + {\gdef\pytx@fvsettings{#2}}% + {\expandafter\gdef\csname pytx@fvsettings@#1\endcsname{#2}}% +}% +% \end{macrocode} +% +% Now that we have a mechanism for applying global settings to typeset \pytex\ code, we go ahead and set a default tab size for all environments. If |\setpythontexfv| is ever invoked, this setting will be overwritten, so that must be kept in mind. +% \begin{macrocode} +\setpythontexfv{tabsize=4} +% \end{macrocode} +% \end{macro}\end{macro} +% +% \begin{macro}{\pytx@FVSet} +% Once the |fancyvrb| settings for \pytex\ are stored in macros, we need a way to actually invoke them. |\pytx@FVSet| applies family-specific settings first, then \pytex-wide settings second, so that \pytex-wide settings have precedence and will override family-specific settings. Note that by using |\fvset|, we are overwriting |fancyvrb|'s settings. Thus, to keep the settings local to the \pytex\ code, |\pytx@FVSet| must always be used within a |\begingroup ... \endgroup| block. +% \begin{macrocode} +\def\pytx@FVSet{% + \expandafter\let\expandafter\pytx@fvsettings@@% + \csname pytx@fvsettings@\pytx@type\endcsname + \ifdefstring{\pytx@fvsettings@@}{}% + {}% + {\expandafter\fvset\expandafter{\pytx@fvsettings@@}}% + \ifdefstring{\pytx@fvsettings}{}% + {}% + {\expandafter\fvset\expandafter{\pytx@fvsettings}}% +} +% \end{macrocode} +% \end{macro} +% + + +% +% \begin{macro}{\pytx@FVB@SaveVerbatim} +% \begin{macro}{pytx@FancyVerbLineTemp} +% |fancyvrb|'s |SaveVerbatim| environment will be used extensively to include code highlighted by Pygments and other processed content. Unfortunately, when the saved content is included in a document with the corresponding |UseVerbatim|, line numbering does not work correctly. Based on a web search, this appears to be a known bug in |fancyvrb|. We begin by fixing this, which requires patching |fancyvrb|'s |\FVB@SaveVerbatim| and |\FVE@SaveVerbatim|. We create a patched |\pytx@FVB@SaveVerbatim| by inserting |\FV@StepLineNo| and |\FV@CodeLineNo=1| at appropriate locations. We also delete an unnecessary |\gdef\SaveVerbatim@Name{#1}|. Then we create a |\pytx@FVE@SaveVerbatim|, and add code so that the two macros work together to prevent |FancyVerbLine| from incorrectly being incremented within the |SaveVerbatim| environment. This involves using the counter |pytx@FancyVerbLineTemp| to temporarily store the value of |FancyVerbLine|, so that it may be restored to its original value after verbatim content has been saved. +% +% Typically, we |\let| our own custom macros to the corresponding macros within |fancyvrb|, but only within a command or environment. In this case, however, we are fixing behavior that should be considered a bug even for normal |fancyvrb| usage. So we let the buggy macros to the patched macros immediately after defining the patched versions. +% \begin{macrocode} +\newcounter{pytx@FancyVerbLineTemp} +% \end{macrocode} +% \begin{macrocode} +\def\pytx@FVB@SaveVerbatim#1{% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \@bsphack + \begingroup + \FV@UseKeyValues + \def\SaveVerbatim@Name{#1}% + \def\FV@ProcessLine##1{% + \expandafter\gdef\expandafter\FV@TheVerbatim\expandafter{% + \FV@TheVerbatim\FV@StepLineNo\FV@ProcessLine{##1}}}% + \gdef\FV@TheVerbatim{\FV@CodeLineNo=1}% + \FV@Scan} +\def\pytx@FVE@SaveVerbatim{% + \expandafter\global\expandafter\let + \csname FV@SV@\SaveVerbatim@Name\endcsname\FV@TheVerbatim + \endgroup\@esphack + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}} +\let\FVB@SaveVerbatim\pytx@FVB@SaveVerbatim +\let\FVE@SaveVerbatim\pytx@FVE@SaveVerbatim +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \subsubsection{Access to printed content (stdout)} +% The |autoprint| package option automatically pulls in printed content from |code| commands and environments. But this does not cover all possible use cases, because we could have print statements/functions in |block| commands and environments as well. Furthermore, sometimes we may print content, but then desire to bring it back into the document multiple times, without duplicating the code that creates the content. Here, we create a number of macros that allow access to printed content. All macros are created in two identical forms, one based on the name |print| and one based on the name |stdout|. Which macros are used depends on user preference. The macros based on |stdout| provide symmetry with |stderr| access. +% +% \begin{macro}{\pytx@stdfile} +% We begin by defining a macro to hold the base name for stdout and stderr content. The name of this file is updated by most commands and environments so that it stays current.\footnote{It is only updated by those commands and environments that interact with |pythontex.py| and thus increment a type-session-group counter so that they can be distinguished. |verb| commands and environments that use |fancyvrb| for typesetting do not interact with |pythontex.py|, do not increment a counter, and thus do not update the stdout file.} It is important, however, to initially set the name empty for error-checking purposes. +% \begin{macrocode} +\def\pytx@stdfile{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@FetchStdoutfile} +% Now we create a generic macro for bringing in the stdout file. This macro can input the content in verbatim form, applying |fancyvrb| options if present. Usage: |\pytx@FetchStdoutfile|\oarg{mode}\oarg{options}\marg{file~path}.\pytxtodo{Error handling for invalid options?} We must disable the macro in the event that the |debug| option is false. Also, the warning text should not be included if we are in the preamble. +% \begin{macrocode} +\def\pytx@stdout@warntext{} +\def\pytx@FetchStdoutfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stdout}{% + \ifstrempty{#1}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{verb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{verbatim}{\VerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \DepyFile{p:\pytx@outputdir/#3.stdout:mode=#1}% + }% + {\pytx@stdout@warntext + \PackageWarning{\pytx@packagename}{Non-existent printed content}}% +} +\ifbool{pytx@opt@stdout}{}{\def\pytx@FetchStdoutfile[#1][#2]#3{}} +\AtBeginDocument{\def\pytx@stdout@warntext{\textbf{??~\pytx@packagename~??}}} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\printpythontex}\begin{macro}{\stdoutpythontex} +% We define a macro that pulls in the content of the most recent stdout file, accepting verbatim settings and also |fancyvrb| settings if they are given. +% \begin{macrocode} +\def\stdoutpythontex{% + \Depythontex{cmd:stdoutpythontex:oo:p}% + \@ifnextchar[{\pytx@Stdout}{\pytx@Stdout[raw]}% +} +\def\pytx@Stdout[#1]{% + \@ifnextchar[{\pytx@Stdout@i[#1]}{\pytx@Stdout@i[#1][]}% +} +\def\pytx@Stdout@i[#1][#2]{% + \pytx@FetchStdoutfile[#1][#2]{\pytx@stdfile}% +} +\def\printpythontex{% + \Depythontex{cmd:printpythontex:oo:p}% + \@ifnextchar[{\pytx@Stdout}{\pytx@Stdout[raw]}% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% \begin{macro}{\saveprintpythontex}\begin{macro}{\savestdoutpythontex} +% Sometimes, we may wish to use printed content at multiple locations in a document. Because |\pytx@stdfile| is changed by every command and environment that could print, the printed content that |\printpythontex| tries to access is constantly changing. Thus, |\printpythontex| is of use only immediately after content has actually been printed, before any additional \pytex\ commands or environments change the definition of |\pytx@stdfile|. To get around this, we create |\saveprintpythontex|\marg{name}. This macro saves the current name of |\pytx@stdfile| so that it is associated with \meta{name} and thus can be retrieved later, after |\pytx@stdfile| has been redefined. +% \begin{macrocode} +\def\savestdoutpythontex{% + \Depythontex{cmd:savestdoutpythontex:m:n}% + \savestdoutpythontex@i +} +\def\savestdoutpythontex@i#1{% + \ifcsname pytx@SVout@#1\endcsname + \PackageError{\pytx@packagename}% + {Attempt to save content using an already-defined name}% + {Use a name that is not already defined}% + \else + \expandafter\edef\csname pytx@SVout@#1\endcsname{\pytx@stdfile}% + \fi +} +\def\saveprintpythontex{% + \Depythontex{cmd:saveprintpythontex:m:n}% + \savestdoutpythontex@i +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% \begin{macro}{\useprintpythontex}\begin{macro}{\usestdoutpythontex} +% Now that we have saved the current |\pytx@stdoutfile| under a new, user-chosen name, we need a way to retrieve the content of that file later, using the name. +% \begin{macrocode} +\def\usestdoutpythontex{% + \Depythontex{cmd:usestdoutpythontex:oom:p}% + \@ifnextchar[{\pytx@UseStdout}{\pytx@UseStdout[]}% +} +\def\pytx@UseStdout[#1]{% + \@ifnextchar[{\pytx@UseStdout@i[#1]}{\pytx@UseStdout@i[#1][]}% +} +\def\pytx@UseStdout@i[#1][#2]#3{% + \ifcsname pytx@SVout@#3\endcsname + \pytx@FetchStdoutfile[#1][#2]{\csname pytx@SVout@#3\endcsname}% + \else + \textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent saved printed content}% + \fi +} +\def\useprintpythontex{% + \Depythontex{cmd:useprintpythontex:oom:p}% + \@ifnextchar[{\pytx@UseStdout}{\pytx@UseStdout[]}% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% \subsubsection{Access to stderr} +% +% We need access to stderr, if it is enabled via the package |makestderr| option. +% +% Both stdout and stderr share the same base file name, stored in |\pytx@stdfile|. Only the file extensions, .stdout and .stderr, differ. +% +% stderr and stdout are treated identically, except that stderr is brought in verbatim by default, while stdout is brought in raw by default. +% +% \begin{macro}{\pytx@FetchStderrfile} +% Create a generic macro for bringing in the stderr file. +% \begin{macrocode} +\def\pytx@FetchStderrfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stderr}{% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stderr}}{}% + \ifstrempty{#1}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{verb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{verbatim}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \DepyFile{p:\pytx@outputdir/#3.stderr:mode=#1}% + }% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent stderr content}}% +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\stderrpythontex} +% We define a macro that pulls in the content of the most recent error file, accepting verbatim settings and also |fancyvrb| settings if they are given. +% \begin{macrocode} +\def\stderrpythontex{% + \Depythontex{cmd:stderrpythontex:oo:p}% + \@ifnextchar[{\pytx@Stderr}{\pytx@Stderr[verbatim]}% +} +\def\pytx@Stderr[#1]{% + \@ifnextchar[{\pytx@Stderr@i[#1]}{\pytx@Stderr@i[#1][]}% +} +\def\pytx@Stderr@i[#1][#2]{% + \pytx@FetchStderrfile[#1][#2]{\pytx@stdfile}% +} +% \end{macrocode} +% \end{macro} +% +% A mechanism is provided for saving and later using stderr. This should be used with care, since stderr content may lose some of its meaning if isolated from the larger code context that produced it. +% +% \begin{macro}{\savestderrpythontex} +% \begin{macrocode} +\def\savestderrpythontex#1{% + \Depythontex{cmd:savestderrpythontex:m:n}% + \ifcsname pytx@SVerr@#1\endcsname + \PackageError{\pytx@packagename}% + {Attempt to save content using an already-defined name}% + {Use a name that is not already defined}% + \else + \expandafter\edef\csname pytx@SVerr@#1\endcsname{\pytx@stdfile}% + \fi +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\usestderrpythontex} +% \begin{macrocode} +\def\usestderrpythontex{% + \Depythontex{cmd:usestderrpythontex:oom:p}% + \@ifnextchar[{\pytx@UseStderr}{\pytx@UseStderr[verb]}% +} +\def\pytx@UseStderr[#1]{% + \@ifnextchar[{\pytx@UseStderr@i[#1]}{\pytx@UseStderr@i[#1][]}% +} +\def\pytx@UseStderr@i[#1][#2]#3{% + \ifcsname pytx@SVerr@#3\endcsname + \pytx@FetchStderrfile[#1][#2]{\csname pytx@SVerr@#3\endcsname}% + \else + \textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent saved stderr content}% + \fi +} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{\texttt{depythontex}} +% +% The purpose of |depythontex| is to create a version of the original \LaTeX\ document that does not rely on the \pytex\ package. All uses of \pytex\ are replaced by their output. This is particularly useful when submitting a paper to a journal, because \pytex\ can simplify the writing process, but many journals frown upon ``special'' packages or custom macros. Note that if you just need to share a \pytex\ document with someone, you can always include a copy of |pythontex.sty| and the \pytex\ output directory with the document, and then non-Python parts of the document can be edited just like a normal \LaTeX\ document, without running any Python code. +% +% The general strategy for |depythontex| is to write an auxiliary file that contains information about all environments and macros that need to be replaced, including location, format, and the content with which they are to be replaced. This auxiliary file is then used to performed substitutions on a copy of the original document. It would be possible to simply create a list of all \pytex\ macros and environments, and use that to perform substitutions. But that approach would have to track the state of \pytex\ more carefully than the auxiliary file approach. For example, in the auxiliary file approach, it is easy to track whether |autoprint| is on or off, because commands and environments will write to the auxiliary file if they do indeed use |autoprint|. But without an auxiliary file, you would have to search for |\setpythontexautoprint| and devise an algorithm for determining where it is on or off. Furthermore, once there is a large set of macros, a general search-and-replace could be quite expensive computationally. +% +% These commands need to be defined after all the other settings commands, because some of the other settings commands are used within this package after being defined, and thus don't need replacement because they're in the package. At the same time, the |depythontex| commands have to exist so that other commands can be defined with them. So dummy versions are created earlier. During the next refactoring, the order will be cleaned up and clarified. +% +% \begin{macro}{\pytx@depyfile} +% If the |depythontex| package option is on, we need to open an auxiliary file for writing depythontex information. +% \begin{macrocode} +\ifbool{pytx@opt@depythontex}{% + \newwrite\pytx@depyfile + \immediate\openout\pytx@depyfile=\jobname.depytx +}{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\Depythontex} +% Each command or environment that is to work with depythontex will write the following information to the auxiliary file: +% \begin{verbatim} +% =>DEPYTHONTEX#<type>:<name>:<args>:<typeset>:<line>:[<Pygments lexer>]# +% \end{verbatim} +% where |<type>| is |cmd| or |env|; |<name>| is the complete name of the command or environment; |<args>| is a string representing the arguments taken (|o|=optional, |m|=mandatory, |v|=mandatory~verbatim, |n|=none); |<typeset>| is a string representing what is typeset (|c|=code, |p|=printed, |n|=null), and |<line>| is |\the\inputlineno|. The last one can be determined automatically without user input, but the first four must be entered when a macro is created. Optionally, the Pygments lexer is written to file if it is available (if |\pytx@lexer| is not |\relax|). These pieces of information are needed for the following reasons. +% \begin{itemize} +% \item |<type>| We need to know whether we are dealing with a command or environment, so we know how to deal with it. There is no way to detect this automatically, since a command could always be inside some environment. +% \item |<name>| We need to know the name of what is to be replaced. There's no way to automatically get this. +% \item |<args>| We need to know the form of the arguments, so we can assemble an appropriate regular expression. In some cases, a command might be created in such a way that this could be detected or easily passed on to \pytex\ (for example, if the command was defined using the |xparse| package), but in general there isn't a simple way to detect it. +% \item |<typeset>| Technically, this could be determined from |\pytx@cmd| in many instances. But it couldn't be determined for cases like |\printpythontex| and |\stderrpythontex|. Furthermore, we want a very general interface suitable for users writing custom commands and environments. +% \item |<line>| This can be determined automatically. +% \item |<Pygments lexer>| This is needed if so that the language can be specified in the output. In general, |\pytx@lexer| can be defined automatically by a command and environment generator. +% \end{itemize} +% +% We need a command that writes this information to the auxiliary file. Since this command may be employed by users writing custom macros, we choose a capitalized name not containing any ampersands |@|. Since we need to be able to easily disable the macro, we create the real macro with name ending in |@orig|, and then |\let| the intended name to it. +% \begin{macrocode} +\let\pytx@lexer\relax +\def\Depythontex@orig#1{% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX\pytx@delimchar#1:% + \the\inputlineno:\ifx\pytx@lexer\relax\else\pytx@lexer\fi\pytx@delimchar}% + \let\pytx@lexer\relax} +\ifbool{pytx@opt@depythontex}% + {\let\Depythontex\Depythontex@orig}% + {\let\Depythontex\@gobble} +\ifbool{pytx@opt@depythontex}{% +\AtEndDocument{% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar version=% + \pytx@packageversion\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar macrofile=% + \pytx@outputdir/\pytx@jobname.pytxmcr\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar outputdir=% + \pytx@outputdir\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar graphicx=% + \ifcsname graphicspath\endcsname true\else false\fi\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar gobble=% + \pytx@opt@gobble\pytx@delimchar}% +}% +}{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\DepyMacro} +% We need a macro that will write the appropriate information to the auxiliary file if substitution with the contents of a macro is needed. The argument is of the form |<typeset>:<macro name>|, where |<typeset>| is the type of content (|p|=print, |c|=code). +% \begin{macrocode} +\def\DepyMacro@orig#1{% + \immediate\write\pytx@depyfile{MACRO:#1}% +} +\ifbool{pytx@opt@depythontex}% + {\let\DepyMacro\DepyMacro@orig}% + {\let\DepyMacro\@gobble} +% \end{macrocode} +% \end{macro} + +% \begin{macro}{\DepyFile} +% We also need a macro that will write the appropriate information to the auxiliary file if substitution with the contents of a file is needed. As an argument, this command takes |<typeset>:<filename>[:mode=<format>]|, where |<typeset>| is the type of content (|p|=print, |c|=code), |<filename>| is the full filename, and the optional |mode| is the format in which the file is brought in (|raw|, |verb|, |verbatim|). If |mode| is not specified, it defaults to reasonable defaults. In general, |mode| is only needed for |p| content; |c| content is verbatim of one form or another. +% \begin{macrocode} +\def\DepyFile@orig#1{% + \immediate\write\pytx@depyfile{FILE:#1}% +}% +\ifbool{pytx@opt@depythontex}% + {\let\DepyFile\DepyFile@orig}% + {\let\DepyFile\@gobble} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\DepyListing} +% We need a macro that will write information to the auxiliary file for code listings, specifically whether line numbers were used, and if so, what the starting number was. This is non-trivial, because it is possible to change both of these via an environment's second optional argument. One approach would be to capture all optional arguments, pass the second to |fancyvrb|, and then attempt to evaluate the status of line numbers via an examination of |fancyvrb|'s internals. That approach would require a good deal of work and would likely involve a patch for |fancyvrb|. Instead, we redefine |\theFancyVerbLine|, so that it saves the line number to file the first time it is used, and then redefines itself to its original form. +% +% Since we are redefining |\theFancyVerbLine|, |\DepyListing| can only be used with commands (such as |\inputpygments|) if it is in a group (|\begingroup| ... |\endgroup|). This prevents the redefinition from ``escaping,'' if line numbering is not used. (Environments are wrapped in groups automatically, so this doesn't apply to them.) +% \begin{macrocode} +\newcommand{\pytx@DepyListing@write}{% + \immediate\write\pytx@depyfile{LISTING:firstnumber=\arabic{FancyVerbLine}}% +} +\def\DepyListing@orig{% + \let\oldFancyVerbLine\theFancyVerbLine + \def\theFancyVerbLine{% + \pytx@DepyListing@write + \expandafter\gdef\expandafter\theFancyVerbLine\expandafter{\oldFancyVerbLine}% + \theFancyVerbLine + }% +} +\ifbool{pytx@opt@depythontex}% + {\let\DepyListing\DepyListing@orig}% + {\let\DepyListing\@empty} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\DepythontexOn} +% \begin{macro}{\DepythontexOff} +% We need a way to switch |depythontex| on and off. When |depythontex| is being used, it needs to be on throughout the entire main document. But it must be switched off for any commands or environments that are brought in via external files (for example, in a package). Since anything that is brought in isn't actually in the text of the main document, substitution is both impossible and unnecessary. +% \begin{macrocode} +\newcommand{\DepythontexOn}{% + \let\Depythontex\Depythontex@orig + \let\DepyMacro\DepyMacro@orig + \let\DepyFile\DepyFile@orig + \let\DepyListing\DepyListing@orig +} +\newcommand{\DepythontexOff}{% + \let\Depythontex\@gobble + \let\DepyMacro\@gobble + \let\DepyFile\@gobble + \let\DepyListing\@empty +} +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Inline commands} +% +% \subsubsection{Inline core macros} +% All inline commands use the same core of inline macros. Inline commands invoke the |\pytx@Inline| macro, and this then branches through a number of additional macros depending on the details of the command and the usage context. |\@ifnextchar| and |\let| are used extensively to control branching. +% +% |\pytx@Inline|, and the macros it calls, perform the following series of operations. +% \begin{itemize} +% \item If there is an optional argument, capture it. The optional argument is the session name of the command. If there is no session name, use the ``|default|'' session. +% \item Determine the delimiting character(s) used for the code encompassed by the command. Any character except for the space character and the opening curly brace |{| may be used as a delimiting character, just as for |\verb|. The opening curly brace |{| may be used, but in this case the closing delimiting character is the closing curly brace |}|. If paired curly braces are used as delimiters, then the code enclosed may only contain paired curly braces. +% \item Using the delimiting character(s), capture the code. Perform some combination of the following tasks: typeset the code, save it to the code file, and bring in content created by the code. +% \end{itemize} +% +% +% \begin{macro}{\pytx@Inline} +% This is the gateway to all inline core macros. It is called by all inline commands. +% Because the delimiting characters could be almost anything, we need to turn off all special category codes before we peek ahead with |\@ifnextchar| to see if an optional argument is present, since |\@ifnextchar| sets the category code of the character it examines. But we set the opening curly brace |{| back to its standard catcode, so that matched braces can be used to capture an argument as usual. The catcode changes are enclosed withing |\begingroup ... \endgroup| so that they may be contained. +% +% The macro |\pytx@InlineOarg| which is called at the end of |\pytx@Inline| takes an argument enclosed by square brackets. If an optional argument is not present, then we supply an empty one, which invokes default treatment in |\pytx@InlineOarg|. +% \begin{macrocode} +\def\pytx@Inline{% + \begingroup + \let\do\@makeother\dospecials + \catcode`\{=1 + \@ifnextchar[{\endgroup\pytx@InlineOarg}{\endgroup\pytx@InlineOarg[]}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineOarg} +% This macro captures the optional argument (or the empty default substitute), which corresponds to the code session. Then it determines whether the delimiters of the actual code are a matched pair of curly braces or a pair of other, identical characters, and calls the next macro accordingly. +% +% We begin by testing for an empty argument (either from the user or from the default empty substitute), and setting the default value if this is indeed the case. It is also possible that the user chose a session name containing a colon. If so, we substitute a hyphen for the colon. This is because temporary files are named based on session, and file names often cannot contain colons. +% +% Then we turn off all special catcodes and set the catcodes of the curly braces back to their default values. This is necessary because we are about to capture the actual code, and we need all special catcodes turned off so that the code can contain any characters. But curly braces still need to be active just in case they are being used as delimiters. We also make the space and tab characters active, since |fancyvrb| needs them that way.\footnote{Part of this may be redundant, since we detokenize later and then retokenize during typesetting if Pygments isn't used. But the detokenizing and saving eliminates tab characters if they aren't active here.} Using |\@ifnextchar| we determine whether the delimiters are curly braces. If so, we proceed to |\pytx@InlineMargBgroup| to capture the code using curly braces as delimiters. If not, we reset the catcodes of the braces and proceed to |\pytx@InlineMargOther|, which uses characters other than the opening curly brace as delimiters. +% \begin{macrocode} +\def\pytx@InlineOarg[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \begingroup + \let\do\@makeother\dospecials + \catcode`\{=1 + \catcode`\}=2 + \catcode`\ =\active + \catcode`\^^I=\active + \@ifnextchar\bgroup + {\pytx@InlineMargBgroup}% + {\catcode`\{=12 + \catcode`\}=12 + \pytx@InlineMargOther}% +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineMargOther}\begin{macro}{\pytx@InlineMargOtherGet} +% This macro captures code delimited by a pair of identical non-brace characters. Then it passes the code on to |\pytx@InlineMargBgroup| for processing. This approach means that the macro definition may be kept concise, and that the processing code must only be defined once. +% +% The macro captures only the next character. This will be the delimiting character. We must begin by ending the group that was left open by |\pytx@InlineOarg|, so that catcodes return to normal. Next we check to see if the delimiting character is a space character. If so, we issue an error, because that is not allowed. If the delimiter is valid, we define a macro |\pytx@InlineMargOtherGet| that will capture all content up to the next delimiting character and pass it to the |\pytx@InlineMargBgroup| macro for processing. That macro does exactly what is needed, except that part of the retokenization is redundant since curly braces were not active when the code was captured. +% +% Once the custom capturing macro has been created, we turn off special catcodes and call the capturing macro. +% \begin{macrocode} +\def\pytx@InlineMargOther#1{% + \endgroup + \ifstrequal{#1}{ }{% + \PackageError{\pytx@packagename}% + {The space character cannot be used as a delimiting character}% + {Choose another delimiting character}}{}% + \def\pytx@InlineMargOtherGet##1#1{\pytx@InlineMargBgroup{##1}}% + \begingroup + \let\do\@makeother\dospecials + \pytx@InlineMargOtherGet +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@InlineMargBgroup} +% We are now ready to capture code using matched curly braces as delimiters, or to process previously captured code that used another delimiting character. +% +% At the very beginning, we must end the group that was left open from |\pytx@InlineOarg| (or by |\pytx@InlineMargOther|), so that catcodes return to normal. +% +% We save a detokenized version of the argument in |\pytx@argdetok|. Even though the argument was captured under special catcode conditions, this is still necessary. If the argument was delimited by curly braces, then any internal curly braces were active when the argument was captured, and these need their catcodes corrected. If the code contains any unicode characters, detokenization is needed so that they may be correctly saved to file. +% +% We save a retokenized version of the argument in |\pytx@argretok|. This is needed for typesetting with |fancyvrb|. The code must be retokenized so that space characters are active, since |fancyvrb| allows space characters to be visible or invisible by making them active. +% +% The \textbf{name} of the counter corresponding to this code is assembled. It is needed for keeping track of the instance, and is used for bringing in content created by the code and for bringing in highlighting created by Pygments. +% +% Next we call a series of macros that determine whether the code is shown (typeset), whether it is saved to the code file, and whether content created by the code (``printed'') should be brought in. These macros are |\let| to appropriate values when an inline command is called; they are not defined independently. +% +% Finally, the counter for the code is incremented. +% \begin{macrocode} +\def\pytx@InlineMargBgroup#1{% + \endgroup + \def\pytx@argdetok{\detokenize{#1}}% + \def\pytx@arg{#1}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \pytx@InlineShow + \pytx@InlineSave + \pytx@InlinePrint + \stepcounter{\pytx@counter}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineShow} +% \begin{macro}{\pytx@InlineSave} +% \begin{macro}{\pytx@InlinePrint} +% The three macros |\pytx@InlineShow|, |\pytx@InlineSave|, and |\pytx@InlinePrint| will be |\let| to appropriate values when commands are called. We must now define the macros to which they may be |\let|. +% \end{macro}\end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@InlineShowFV} +% Code may be typeset with |fancyvrb|. |fancyvrb| settings are invoked via |pytx@FVSet|, but this must be done within a group so that the settings remain local. Most of the remainder of the commands are from |fancyvrb|'s |\FV@FormattingPrep|, and take care of various formatting matters, including spacing, font, whether space characters are shown, and any user-defined formatting. Finally, we create an |\hbox| and invoke |\FancyVerbFormatLine| to maintain parallelism with |BVerbatim|, which is used for inline content highlighted with Pygments. |\FancyVerbFormatLine| may be redefined to alter the typeset code, for example, by putting it in a colorbox via the following command:\footnote{Currently, |\textbackslash FancyVerbFormatLine| is global, as in |fancyvrb|. Allowing a family-specific variant may be considered in the future. In most cases, the |fancyvrb| option |formatcom|, combined with external formatting from packages like |mdframed|, should provide all formatting desired. But something family-specific might occasionally prove useful.} +% \begin{quote} +% |\renewcommand{\FancyVerbFormatLine}[1]{\colorbox{green}{#1}}| +% \end{quote} +% \begin{macrocode} +\def\pytx@InlineShowFV{% + \def\pytx@argretok{% + \begingroup + \everyeof{\noexpand}% + \endlinechar-1\relax + \let\do\@makeother\dospecials + \catcode`\ =\active + \catcode`\^^I=\active + \expandafter\scantokens\expandafter{\pytx@arg}% + \endgroup}% + \begingroup + \pytx@FVSet + \FV@BeginVBox + \frenchspacing + \FV@SetupFont + \FV@DefineWhiteSpace + \FancyVerbDefineActive + \FancyVerbFormatCom + \FV@ObeyTabsInit + \hbox{\FancyVerbFormatLine{\pytx@argretok}}% + \FV@EndVBox + \endgroup +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineShowPyg} +% Code may be typeset with Pygments. Processed Pygments content is saved in the .pytxmcr file, wrapped in |fancyvrb|'s |SaveVerbatim| environment. The content is then restored, in a form suitable for inline use, via |BUseVerbatim|. Unlike non-inline content, which may be brought in either via macro or via separate external file, inline content is always brought in via macro. The counter |pytx@FancyVerbLineTemp| is used to prevent |fancyvrb|'s line count from being affected by \pytex\ content. A group is necessary to confine the |fancyvrb| settings created by |\pytx@FVSet|. +% \begin{macrocode} +\def\pytx@InlineShowPyg{% + \begingroup + \pytx@FVSet + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \BUseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \else + \textbf{??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}% + \fi + \endgroup +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineSaveCode} +% This macro writes \pytex\ information to the code file and then writes the actual code. +% \begin{macrocode} +\def\pytx@InlineSaveCode{% + \pytx@WriteCodefileInfo + \immediate\write\pytx@codefile{\pytx@argdetok}% +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineAutoprint} +% This macro brings in printed content automatically, if the package |autoprint| option is true. Otherwise, it does nothing. We must disable the macro in the event that the |debug| option is false. We wait until the beginning of the document to create the real macro, since any code commands and environments in the preamble shouldn't be printing and in any case we can't know what the outputdir is until the beginning of the document. +% +% \begin{macrocode} +\let\pytx@InlineAutoprint\@empty +\AtBeginDocument{ + \def\pytx@InlineAutoprint{% + \ifbool{pytx@opt@autoprint}{% + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.stdout}% + {\DepyFile{p:\pytx@outputdir/\pytx@stdfile.stdout}}{}}{}% + } + \ifbool{pytx@opt@stdout}{}{\let\pytx@InlineAutoprint\@empty} +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@InlineMacroprint} +% This macro brings in ``printed'' content that is brought in via macros in the .pytxmcr file. We must disable the macro in the event that the |debug| option is false. +% \begin{macrocode} +\def\pytx@InlineMacroprint{% + \edef\pytx@mcr{pytx@MCR@\pytx@type @\pytx@session @\pytx@group @\arabic{\pytx@counter}}% + \ifcsname\pytx@mcr\endcsname + \csname\pytx@mcr\endcsname + \DepyMacro{p:\pytx@mcr}% + \else + \textbf{??}% + \PackageWarning{\pytx@packagename}{Missing autoprint content}% + \fi +} +\ifbool{pytx@opt@stdout}{}{\let\pytx@InlineMacroprint\@empty} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@InlineMacroprintFV} +% This macro brings in ``printed'' content that is brought in via |SaveVerbatim| (only inline console references at the moment). We must disable the macro in the event that the |debug| option is false. +% \begin{macrocode} +\def\pytx@InlineMacroprintFV{% + \edef\pytx@mcr{pytx@\pytx@type @\pytx@session @\pytx@group @\arabic{\pytx@counter}}% + \ifcsname FV@SV@\pytx@mcr\endcsname + \BUseVerbatim{\pytx@mcr}% + \DepyMacro{c:\pytx@mcr}% + \else + \textbf{??}% + \PackageWarning{\pytx@packagename}{Missing autoprint content}% + \fi +} +\ifbool{pytx@opt@stdout}{}{\let\pytx@InlineMacroprint\@empty} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Inline command constructors} +% With the core inline macros complete, we are ready to create constructors for different kinds of inline commands. All of these consctructors take a string and define an inline command named using that string as a base name. Two forms of each constructor are created, one that uses Pygments and one that does not. The Pygments variants have names ending in ``Pyg''. +% +% \begin{macro}{\pytx@MakeInlinebFV}\begin{macro}{\pytx@MakeInlinebPyg} +% These macros creates inline block commands, which both typeset code and save it so that it may be executed. The base name of the command is stored in |\pytx@type|. A string representing the kind of command is stored in |\pytx@cmd|. Then |\pytx@SetContext| is used to set |\pytx@context| and |\pytx@SetGroup| is used to set |\pytx@group|. Macros for showing, saving, and printing are set to appropriate values. Then the core inline macros are invoked through |\pytx@Inline|. +% \begin{macrocode} +\newcommand{\pytx@MakeInlinebFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1b\endcsname}{% + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{cmd:#1b:ov:c}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{b}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\pytx@InlineShowFV + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +\newcommand{\pytx@MakeInlinebPyg}[1]{% + \expandafter\newcommand\expandafter{\csname #1b\endcsname}{% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{b}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\pytx@InlineShowPyg + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@MakeInlinevFV}\begin{macro}{\pytx@MakeInlinevPyg} +% This macro creates inline verbatim commands, which only typeset code. |\pytx@type|, |\pytx@cmd|, |\pytx@context|, and |\pytx@group| are still set, for symmetry with other commands. They are not needed for |fancyvrb| typesetting, though. We use |\pytx@SetGroupVerb| to split verbatim content (|v| and |verb|) off into its own group. That way, verbatim content doesn't affect the instance numbers of executed code, and thus executed code is not affected by the addition or removal of verbatim content. +% \begin{macrocode} +\newcommand{\pytx@MakeInlinevFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1v\endcsname}{% + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{cmd:#1v:ov:c}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowFV + \let\pytx@InlineSave\@empty + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +\newcommand{\pytx@MakeInlinevPyg}[1]{% + \expandafter\newcommand\expandafter{\csname #1v\endcsname}{% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowPyg + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@MakeInlinecFV}\begin{macro}{\pytx@MakeInlinecPyg} +% This macro creates inline code commands, which save code for execution but do not typeset it. If the code prints content, this content is inputted automatically if the package option |autoprint| is on. Since no code is typeset, there is no difference between the |fancyvrb| and Pygments forms. +% \begin{macrocode} +\newcommand{\pytx@MakeInlinecFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1c\endcsname}{% + \Depythontex{cmd:#1c:ov:p}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{c}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\pytx@InlineAutoprint + \pytx@Inline + }% +}% +\let\pytx@MakeInlinecPyg\pytx@MakeInlinecFV +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@MakeInlineFV}\begin{macro}{\pytx@MakeInlinePyg} +% This macro creates plain inline commands, which save code and then bring in the output of |pytex.formatter(|\meta{code}|)| (|pytex.formatter()| is the formatter function in Python sessions that is provided by |pythontex_utils*.py|). The Python output is saved in a \TeX\ macro, and the macro is written to a file shared by all \pytex\ sessions. This greatly reduces the number of external files needed. Since no code is typeset, there is no difference between the |fancyvrb| and Pygments forms. +% \begin{macrocode} +\newcommand{\pytx@MakeInlineFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1\endcsname}{% + \Depythontex{cmd:#1:ov:p}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{i}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\pytx@InlineMacroprint + \pytx@Inline + }% +}% +\let\pytx@MakeInlinePyg\pytx@MakeInlineFV +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@MakeInlineConsFV}\begin{macro}{\pytx@MakeInlineConsPyg} +% This is the inline form for |console| types. It brings in |SaveVerbatim|. +% \begin{macrocode} +\newcommand{\pytx@MakeInlineConsFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1\endcsname}{% + \Depythontex{cmd:#1:ov:c}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{i}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\pytx@InlineMacroprintFV + \pytx@Inline + }% +}% +\let\pytx@MakeInlineConsPyg\pytx@MakeInlineConsFV +% \end{macrocode} +% \end{macro}\end{macro} +% +% +%\begin{macro}{\pythontexcustomc} +% This macro takes a single line of code and adds it to all sessions within a family. It is the inline version of the |pythontexcustomcode| environment. +% \begin{macrocode} +\newcommand{\pythontexcustomc}[2][begin]{% + \Depythontex{cmd:pythontexcustomc:omv:p}% + \ifstrequal{#1}{begin}{}{% + \ifstrequal{#1}{end}{}{\PackageError{\pytx@packagename}% + {Invalid optional argument for \string\pythontexcustomc}{} + }% + }% + \xdef\pytx@type{CC:#2:#1}% + \edef\pytx@cmd{c}% + \def\pytx@context{}% + \def\pytx@group{none}% + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline[none]% +}% +% \end{macrocode} +% \end{macro} +% +% +%\begin{macro}{\setpythontexcustomcode} +% This macro is a holdover from 0.9beta3. It has been deprecated in favor of |\pythontexcustomc| and |pythontexcustomcode|. +% \begin{macrocode} +\def\setpythontexcustomcode#1{% + \Depythontex{cmd:setpythontexcustomcode:mv:p}% + \PackageWarning{\pytx@packagename}{The command + \string\setpythontexcustomcode\space has been deprecated; + use \string\pythontexcustomc\space or pythontexcustomcode instead}% + \begingroup + \let\do\@makeother\dospecials + \catcode`\{=1 + \catcode`\}=2 + \catcode`\^^M=10\relax + \pytx@SetCustomCode{#1}% +} +\long\def\pytx@SetCustomCode#1#2{% + \endgroup + \pythontexcustomc{#1}{pythontexcustomcode=[#2]; + exec('for expr in pythontexcustomcode: exec(expr)'); + del(pythontexcustomcode)} +} +\@onlypreamble\setpythontexcustomcode +% \end{macrocode} +% \end{macro} +% +% +% \subsection{Environments} +% +% The inline commands were all created using a common core set of macros, combined with short, command-specific constructors. In the case of environments, we do not have a common core set of macros. Each environment is coded separately, though there are similarities among environments. In the future, it may be worthwhile to attempt to consolidate the environment code base. +% +% One of the differences between inline commands and environments is that environments may need to typeset code with line numbers. Each family of code needs to have its own line numbering (actually, its own numbering for code, verbatim, and console groups), and this line numbering should not overwrite any line numbering that may separately be in use by |fancyvrb|. To make this possible, we use a temporary counter extensively. When line numbers are used, |fancyvrb|'s line counter is copied into |pytx@FancyVerbLineTemp|, lines are numbered, and then |fancyvrb|'s line counter is restored from |pytx@FancyVerbLineTemp|. This keeps |fancyvrb| and \pytex's line numbering separate, even though \pytex\ is using |fancyvrb| and its macros internally. +% +% \subsubsection{Block and verbatim environment constructors} +% We begin by creating |block| and |verb| environment constuctors that use |fancyvrb|. Then we create Pygments versions. +% +% \begin{macro}{\pytx@FancyVerbGetLine} +% The |block| environment needs to both typeset code and save it so it can be executed. |fancyvrb| supports typesetting, but doesn't support saving at the same time. So we create a modified version of |fancyvrb|'s |\FancyVerbGetLine| macro which does. This is identical to the |fancyvrb| version, except that we add a line that writes to the code file. The material that is written is detokenized to avoid catcode issues and make unicode work correctly. +% \begin{macrocode} +\begingroup +\catcode`\^^M=\active +\gdef\pytx@FancyVerbGetLine#1^^M{% + \@nil% + \FV@CheckEnd{#1}% + \ifx\@tempa\FV@EnvironName% + \ifx\@tempb\FV@@@CheckEnd\else\FV@BadEndError\fi% + \let\next\FV@EndScanning% + \else% + \def\FV@Line{#1}% + \def\next{\FV@PreProcessLine\FV@GetLine}% + \immediate\write\pytx@codefile{\detokenize{#1}}% + \fi% + \next}% +\endgroup +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@MakeBlockFV} +% Now we are ready to actually create block environments. This macro takes an environment base name \meta{name} and creates a block environment \meta{name}|block|, using |fancyvrb|. +% +% The block environment is a |Verbatim| environment, so we declare that with the |\VerbatimEnvironment| macro, which lets |fancyvrb| find the end of the environment correctly. We define the type, define the command, and set the context and group. +% +% We need to check for optional arguments, so we begin a group and use |\obeylines| to make line breaks active. Then we check to see if the next char is an opening square bracket. If so, there is an optional argument, so we end our group and call the |\pytx@BeginBlockEnvFV| macro, which will capture the argument and finish preparing for the block content. If not, we end the group and call the same |\pytx@BeginBlockEnvFV| macro with an empty argument. The line breaks need to be active during this process because we don't care about content on the next line, including opening square brackets on the next line; we only care about content in the line on which the environment is declared, because only on that line should there be an optional argument. The problem is that since we are dealing with code, it is quite possible for there to be an opening square bracket at the beginning of the next line, so we must prevent that from being misinterpreted as an optional argument. +% +% After the environment, we need to clean up several things. Much of this relates to what is done in the |\pytx@BeginBlockEnvFV| macro. The body of the environment is wrapped in a |Verbatim| environment, so we must end that. It is also wrapped in a group, so that |fancyvrb| settings remain local; we end the group. Then we define the name of the outfile for any printed content, so that it may be accessed by |\printpythontex| and company. Finally, we rearrange counters. The current code line number needs to be stored in |\pytx@linecount|, which was defined to be specific to the current type-session-group set. The |fancyvrb| line number needs to be set back to its original value from before the environment began, so that \pytex\ content does not affect the line numbering of |fancyvrb| content. Finally, the |\pytx@counter|, which keeps track of commands and environments within the current type-session-group set, needs to be incremented. +% +% \begin{macrocode} +\newcommand{\pytx@MakeBlockFV}[1]{% + \expandafter\newenvironment{#1block}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1block:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{block}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginBlockEnvFV}{\endgroup\pytx@BeginBlockEnvFV[]}% + }% + {\end{Verbatim}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BeginBlockEnvFV} +% This macro finishes preparations to actually begin the block environment. It captures the optional argument (or the empty argument supplied by default). If this argument is empty, then it sets the value of the argument to the default value. If not, then colons in the optional argument are replaced with underscores, and the modified argument is stored in |\pytx@session|. Colons are replaced with underscores because session names must be suitable for file names, and colons are generally not allowed in file names. However, we want to be able to \textit{enter} session names containing colons, since colons provide a conventient method of indicating relationships, and are commonly used in \LaTeX\ labels. For example, we could have a session named |plots:specialplot|. +% +% Once the session is established, we are free to define the counter for the current type-session-group, and make sure it exists. We also define the counter that will keep track of line numbers for the current type-session-group, and make sure it exists. Then we do some counter trickery. We don't want |fancyvrb| line counting to be affected by \pytex\ content, so we store the current line number held by |FancyVerbLine| in |pytx@FancyVerbLineTemp|; we will restore |FancyVerbLine| to this original value at the end of the environment. Then we set |FancyVerbLine| to the appropriate line number for the current type-session-group. This provides proper numbering continuity between different environments within the same type-session-group. +% +% Next, we write environment information to the code file, now that all the necessary information is assembled. We begin a group, to keep some things local. We |\let| a |fancyvrb| macro to our custom macro. We set |fancyvrb| settings to those of the current type using |\pytx@FVSet|. Once this is done, we are finally ready to start the |Verbatim| environment. Note that the |Verbatim| environment will capture a second optional argument delimited by square brackets, if present, and apply this argument as |fancyvrb| formatting. Thus, the environment actually takes up to two optional arguments, but if you want to use |fancyvrb| formatting, you must supply an empty (default session) or named (custom session) optional argument for the \pytex\ code. +% \begin{macrocode} +\def\pytx@BeginBlockEnvFV[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@WriteCodefileInfo + \let\FancyVerbGetLine\pytx@FancyVerbGetLine + \pytx@FVSet + \begin{Verbatim}% +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@MakeVerbFV} +% The |verbatim| environments only typeset code; they do not save it for execution. Thus, we just use a standard |fancyvrb| environment with a few enhancements. +% +% As in the |block| environment, we declare that we are using a |Verbatim| environment, define type and command, set context and group (note the use of the |Verb| group), and take care of optional arguments before calling a macro to wrap things up (in this case, |\pytx@BeginVerbEnvFV|). Currently, much of the saved information is unused, but it is provided to maintain parallelism with the |block| environment. +% +% Ending the environment involves ending the |Verbatim| environment begun by |\pytx@BeginVerbEnvFV|, ending the group that kept |fancyvrb| settings local, and resetting counters. We define a |stdfile| and step the counter, even though there will never actually be any output to pull in, to force |\printpythontex| and company to be used immediately after the code they refer to and to maintain parallelism. +% \begin{macrocode} +\newcommand{\pytx@MakeVerbFV}[1]{% + \expandafter\newenvironment{#1verbatim}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1verbatim:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{verbatim}% + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginVerbEnvFV}{\endgroup\pytx@BeginVerbEnvFV[]}% + }% + {\end{Verbatim}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BeginVerbEnvFV} +% This macro captures the optional argument of the environment (or the default empty argument that is otherwise supplied). If the argument is empty, it assignes a default value; otherwise, it substitutes underscores for colons in the argument. The argument is assigned to |\pytx@session|. A line counter is created, and its existence is checked. We do the standard line counter trickery. Then we begin a group to keep |fancyvrb| settings local, invoke the settings via |\pytx@FVSet|, and begin the |Verbatim| environment. +% \begin{macrocode} +\def\pytx@BeginVerbEnvFV[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \begin{Verbatim}% +} +% \end{macrocode} +% \end{macro} +% +% Now for the Pygments forms of |block| and |verb|. Since all code must be saved now (either to be executed or processed by Pygments, or both), the environment code may be simplified compared to the non-Pygments case. +% \begin{macro}{\pytx@MakePygEnv} +% The |block| and |verb| environments are created via the same macro. The |\pytx@MakePygEnv| macro takes two arguments: first, the code type, and second, the environment (|block| or |verb|). The reason for using the same macro is that both must now save their code externally, and bring back the result typeset by Pygments. Thus, on the \LaTeX\ side, their behavior is identical. The only difference is on the Python side, where the block code is executed and thus there may be output available via |\printpythontex| and company. +% +% The actual workings of the macro are a combination of those of the non-Pygments macros, so please refer to those for details. The only exception is the code for bringing in Pygments output, but this is done using almost the same approach as that used for the inline Pygments commands. There are two differences: first, the |block| and |verb| environments use |\UseVerbatim| rather than |\BUseVerbatim|, since they are not typesetting code inline; and second, they accept a second, optional argument containing |fancyvrb| commands and this is used in typesetting the saved content. Any |fancyvrb| commands are saved in |\pytx@fvopttmp| by |\pytx@BeginEnvPyg@i|, and then used when the code is typeset. +% +% Note that the positioning of all the |FancyVerbLine| trickery in what follows is significant. Saving the |FancyVerbLine| counter to a temporary counter before the beginning of |VerbatimOut| is important, because otherwise the |fancyvrb| numbering can be affected. +% +% \begin{macrocode} +\newcommand{\pytx@MakePygEnv}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \xdef\pytx@type{#1}% + \edef\pytx@cmd{#2}% + \pytx@SetContext + \ifstrequal{#2}{block}{\pytx@SetGroup}{} + \ifstrequal{#2}{verbatim}{\pytx@SetGroupVerb}{} + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginEnvPyg}{\endgroup\pytx@BeginEnvPyg[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BeginEnvPyg} +% This macro finishes preparing for the content of a |verb| or |block| environment with Pygments content. It captures an optional argument corresponding to the session name and sets up instance and line counters. Finally, it calls an additional macro that handles the possibility of a second optional argument. +% \begin{macrocode} +\def\pytx@BeginEnvPyg[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@WriteCodefileInfo + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginEnvPyg@i}{\endgroup\pytx@BeginEnvPyg@i[]}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BeginEnvPyg@i} +% This macro captures a second optional argument, corresponding to |fancyvrb| options. Note that not all |fancyvrb| options may be passed to saved content when it is actually used, particularly those corresponding to how the content was read in the first place (for example, command characters). But at least most formatting options such as line numbering work fine. As with the non-Pygments environments, |\begin{VerbatimOut}| doesn't take a second mandatory argument, since we are using a custom version and don't need to specify the file in which Verbatim content is saved. It is important that the |FancyVerbLine| saving be done here; if it is done later, after the end of |VerbatimOut|, then numbering can be off in some circumstances (for example, a single |pyverbatim| between two |Verbatim|'s). +% \begin{macrocode} +\def\pytx@BeginEnvPyg@i[#1]{% + \def\pytx@fvopttmp{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +}% +% \end{macrocode} +% \end{macro} +% +% Since we are using the same code to create both |block| and |verb| environments, we now create a specific macro for creating each case, to make usage equivalent to that for the non-Pygments case. +% \begin{macro}{\pytx@MakeBlockPyg} +% The block environment is constructed via the |\pytx@MakePygEnv| macro. +% \begin{macrocode} +\newcommand{\pytx@MakeBlockPyg}[1]{\pytx@MakePygEnv{#1}{block}} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@MakeVerbPyg} +% The verb environment is constructed likewise. +% \begin{macrocode} +\newcommand{\pytx@MakeVerbPyg}[1]{\pytx@MakePygEnv{#1}{verbatim}} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Code environment constructor} +% The |code| environment merely saves code to the code file; nothing is typeset. To accomplish this, we use a slightly modified version of |fancyvrb|'s |VerbatimOut|. +% \begin{macro}{\pytx@WriteDetok} +% We can use |fancyvrb| to capture the code, but we will need a way to write the code in detokenized form. This is necessary so that \TeX\ doesn't try to process the code as it is written, which would generally be disastrous. +% \begin{macrocode} +\def\pytx@WriteDetok#1{% + \immediate\write\pytx@codefile{\detokenize{#1}}}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@FVB@VerbatimOut} +% We need a custom version of the macro that begins |VerbatimOut|. We don't need |fancyvrb|'s key values, and due to our use of |\detokenize| to write content, we don't need its space and tab treatment either. We do need |fancyvrb| to write to our code file, not the file to which it would write by default. And we don't need to open any files, because the code file is already open. These last two are the only important differences between our version and the original |fancyvrb| version. Since we don't need to write to a user-specified file, we don't require the mandatory argument of the original macro. +% \begin{macrocode} +\def\pytx@FVB@VerbatimOut{% + \@bsphack + \begingroup + \let\FV@ProcessLine\pytx@WriteDetok + \let\FV@FontScanPrep\relax + \let\@noligs\relax + \FV@Scan}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@FVE@VerbatimOut} +% Similarly, we need a custom version of the macro that ends |VerbatimOut|. We don't want to close the file to which we are saving content. +% \begin{macrocode} +\def\pytx@FVE@VerbatimOut{\endgroup\@esphack}% +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@EnvAutoprint} +% We also need a macro for bringing in autoprint content. This is a separate macro so that it can be easily disabled by the package option |debug|. We wait until the beginning of the document to create the real macro, since any code commands and environments in the preamble shouldn't be printing and in any case we can't know what the outputdir is until the beginning of the document. +% \begin{macrocode} +\let\pytx@EnvAutoprint\@empty +\AtBeginDocument{ + \def\pytx@EnvAutoprint{% + \ifbool{pytx@opt@autoprint}{% + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.stdout}% + {\DepyFile{p:\pytx@outputdir/\pytx@stdfile.stdout}}{}}{}% + } + \ifbool{pytx@opt@stdout}{}{\let\pytx@EnvAutoprint\@empty} +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@MakeCodeFV} +% Now that the helper macros for the |code| environment have been defined, we are ready to create the macro that makes |code| environments. Everything at the beginning of the environment is similar to the |block| and |verb| environments. +% +% After the environment, we need to close the |VerbatimOut| environment begun by |\pytx@BeginCodeEnv@i| and end the group it began. We define the outfile, and bring in any printed content if the |autoprint| setting is on. We must still perform some |FancyVerbLine| trickery to prevent the |fancyvrb| line counter from being affected by \textbf{writing} content! Finally, we step the counter. +% \begin{macrocode} +\newcommand{\pytx@MakeCodeFV}[1]{% + \expandafter\newenvironment{#1code}{% + \VerbatimEnvironment + \Depythontex{env:#1code:oo|:p}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{code}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginCodeEnv}{\endgroup\pytx@BeginCodeEnv[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \pytx@EnvAutoprint + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BeginCodeEnv} +% This macro finishes setting things up before the |code| environment contents. It processes the optional argument, defines a counter and checks its existence, writes info to the code file, and then calls the |\pytx@BeginCodeEnv@i| macro. This macro is necessary so that the environment can accept two optional arguments. Since the |block| and |verb| environments can accept two optional arguments (the first is the name of the session, the second is |fancyvrb| options), the code environment also should be able to, to maintain parallelism (for example, |pyblock| should be able to be swapped with |pycode| without changing environment arguments---it should just work). However, |VerbatimOut| doesn't take an optional argument. So we need to capture and discard any optional argument, before starting |VerbatimOut|. +% \begin{macrocode} +\def\pytx@BeginCodeEnv[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \pytx@WriteCodefileInfo + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginCodeEnv@i}{\endgroup\pytx@BeginCodeEnv@i[]}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BeginCodeEnv@i} +% As described above, this macro captures a second optional argument, if present, and then starts the |VerbatimOut| environment. Note that |VerbatimOut| does not have a mandatory argument, because we are invoking our custom |\pytx@FVB@VerbatimOut| macro. The default |fancyvrb| macro needs an argument to tell it the name of the file to which to save the verbatim content. But in our case, we are always writing to the same file, and the custom macro accounts for this by not having a mandatory file name argument. We must perform the typical |FancyVerbLine| trickery, to prevent the |fancyvrb| line counter from being affected by \textbf{writing} content! +% \begin{macrocode} +\def\pytx@BeginCodeEnv@i[#1]{% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@MakeCodePyg} +% Since the code environment simply saves code for execution and typesets nothing, the Pygments version is identical to the non-Pygments version, so we simply let the former to the latter. +% \begin{macrocode} +\let\pytx@MakeCodePyg\pytx@MakeCodeFV +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{pythontexcustomcode} +% This environment is used for adding custom code to all sessions within a command and environment family. It is the environment equivalent of the inline command |\pythontexcustomc|. +% \begin{macrocode} +\newenvironment{pythontexcustomcode}[2][begin]{% + \VerbatimEnvironment + \Depythontex{env:pythontexcustomcode:om:n}% + \ifstrequal{#1}{begin}{}{% + \ifstrequal{#1}{end}{}{\PackageError{\pytx@packagename}% + {Invalid optional argument for pythontexcustomcode}{} + }% + }% + \xdef\pytx@type{CC:#2:#1}% + \edef\pytx@cmd{code}% + \def\pytx@context{}% + \def\pytx@group{none}% + \pytx@BeginCodeEnv[none]}% +{\end{VerbatimOut}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Console environment constructor} +% +% The |console| environment needs to write all code contained in the environment to the code file, and then bring in the console output. +% +% An environment suffix is not enforced for flexibility. For Python, the convention is that |console| type names will end with |con|, and then the environment will use the suffix |sole|. For example, the |pycon| type has the |pyconsole| environment. +% \begin{macro}{\pytx@MakeConsoleFV} +% \begin{macrocode} +\newcommand{\pytx@MakeConsFV}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1#2:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{console}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginConsEnvFV}{\endgroup\pytx@BeginConsEnvFV[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \DepyMacro{c:\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.tex}% + {\DepyFile{c:\pytx@outputdir/\pytx@stdfile.tex}}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent console content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@BeginConsEnvFV} +% \begin{macrocode} +\def\pytx@BeginConsEnvFV[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@WriteCodefileInfo + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginConsEnvFV@i}{\endgroup\pytx@BeginConsEnvFV@i[]}% +}% +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@BeginConsEnvFV@i} +% \begin{macrocode} +\def\pytx@BeginConsEnvFV@i[#1]{% + \def\pytx@fvopttmp{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +}% +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@MakeConsPyg} +% The Pygments version of the |console| environment is identical to the |fancyvrb| version, except that |.pygtex| rather than |.tex| files are brought in. +% \begin{macrocode} +\newcommand{\pytx@MakeConsPyg}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1#2:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{console}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginConsEnvFV}{\endgroup\pytx@BeginConsEnvFV[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \DepyMacro{c:\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.pygtex}% + {\DepyFile{c:\pytx@outputdir/\pytx@stdfile.pygtex}}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent console content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +% \end{macrocode} +% \end{macro} +% +% +% \subsection{Constructors for command and environment families} +% Everything is now in place to create commands and environments, with and without Pygments usage. To make all of this more readily usable, we need macros that will create a whole family of commands and environments at once, using a base name. For example, we need a way to create all commands and environments based off of the |py| base name. +% +% \begin{macro}{\makepythontexfamily} +% This macro creates a family of commands. It needs a some |pgfkeys| to handle the optional arguments. The actual creation of all non-code commands and environments is delayed using |\AtBeginDocument|, so that the user has the option to choose whether |fancyvrb| or Pygments is used for the family. +% \begin{macrocode} +\pgfkeys{ + /PYTX/family/.is family, + /PYTX/family, + prettyprinter/.estore in = \pytx@tmp@pprinter, + pyglexer/.estore in = \pytx@tmp@pyglexer, + pygopt/.estore in = \pytx@tmp@pygopt, + console/.estore in = \pytx@tmp@console, + default/.style = {prettyprinter=auto, pyglexer=text, pygopt={}, console=false} +} +\def\pytx@MakeFamilyFV#1{% + \pytx@MakeInlinebFV{#1}% + \pytx@MakeInlinevFV{#1}% + \pytx@MakeInlineFV{#1}% + \pytx@MakeBlockFV{#1}% + \pytx@MakeVerbFV{#1}% +} +\def\pytx@MakeFamilyPyg#1{% + \ifbool{pytx@opt@pyginline}% + {\pytx@MakeInlinebPyg{#1}\pytx@MakeInlinevPyg{#1}}% + {\pytx@MakeInlinebFV{#1}\pytx@MakeInlinevFV{#1}}% + \pytx@MakeInlinePyg{#1}% + \pytx@MakeBlockPyg{#1}% + \pytx@MakeVerbPyg{#1}% + \booltrue{pytx@usedpygments}% + \AtEndDocument{\immediate\write\pytx@codefile{pygfamily=#1|% + \csname pytx@pyglexer@#1\endcsname|% + \csname pytx@pygopt@#1\endcsname}% + }% +} +\def\pytx@MakeFamilyFVCons#1{% + \pytx@MakeInlinevFV{#1}% + \pytx@MakeInlineConsFV{#1}% + \pytx@MakeConsFV{#1}{sole}% + \pytx@MakeVerbFV{#1}% +} +\def\pytx@MakeFamilyPygCons#1{% + \ifbool{pytx@opt@pyginline}% + {\pytx@MakeInlinevPyg{#1}}% + {\pytx@MakeInlinevFV{#1}}% + \pytx@MakeInlineConsPyg{#1}% + \pytx@MakeConsPyg{#1}{sole}% + \pytx@MakeVerbPyg{#1}% + \booltrue{pytx@usedpygments}% + \AtEndDocument{\immediate\write\pytx@codefile{pygfamily=#1|% + \csname pytx@pyglexer@#1\endcsname|% + \csname pytx@pygopt@#1\endcsname}% + }% +} +\newcommand{\makepythontexfamily}[2][]{% + \IfBeginWith{#2}{PYG}% + {\PackageError{\pytx@packagename}% + {Attempt to create macros with reserved prefix PYG}{}}{}% + \pgfkeys{/PYTX/family, default, #1} + \expandafter\xdef\csname pytx@macroformatter@#2\endcsname{\pytx@tmp@pprinter} + \expandafter\gdef\csname pytx@fvsettings@#2\endcsname{} + \expandafter\xdef\csname pytx@pyglexer@#2\endcsname{\pytx@tmp@pyglexer} + \expandafter\xdef\csname pytx@pygopt@#2\endcsname{\pytx@tmp@pygopt} + \expandafter\xdef\csname pytx@console@#2\endcsname{\pytx@tmp@console} + \pytx@MakeInlinecFV{#2} + \pytx@MakeCodeFV{#2} + \AtBeginDocument{% + \ifcsstring{pytx@macroformatter@#2}{auto}{% + \ifbool{pytx@opt@pygments}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyPygCons{#2}}{\pytx@MakeFamilyPyg{#2}}}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyFVCons{#2}}{\pytx@MakeFamilyFV{#2}}}% + }{}% + \ifcsstring{pytx@macroformatter@#2}{fancyvrb}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyFVCons{#2}}{\pytx@MakeFamilyFV{#2}}}{}% + \ifcsstring{pytx@macroformatter@#2}{pygments}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyPygCons{#2}}{\pytx@MakeFamilyPyg{#2}}}{}% + }% +} +\@onlypreamble\makepythontexfamily +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\setpythontexpyglexer} +% We need to be able to reset the lexer associated with a family after the family has already been created. +% \begin{macrocode} +\newcommand{\setpythontexpyglexer}[2][]{% + \Depythontex{cmd:setpythontexpyglexer:om:n}% + \ifstrempty{#1}{\def\pytx@pyglexer{#2}}{% + \ifcsname pytx@pyglexer@#1\endcsname + \expandafter\xdef\csname pytx@pyglexer@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify a non-existent family}{}% + \fi + }% +}% +\@onlypreamble\setpythontexpyglexer +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\setpythontexpygopt} +% The user may wish to modify the Pygments options associated with a family. This macro takes two arguments: first, the family base name; and second, the Pygments options to associate with the family. This macro is particularly useful in changing the Pygments style of default command and environment families. +% +% Due to the implementation (and also in the interest of keeping typesetting consistent), the Pygments style for a family must remain constant throughout the document. Thus, we only allow changes to the style in the preamble. +% \begin{macrocode} +\newcommand{\setpythontexpygopt}[2][]{% + \Depythontex{cmd:setpythontexpygopt:om:n}% + \ifstrempty{#1}{\def\pytx@pygopt{#2}}{% + \ifcsname pytx@pygopt@#1\endcsname + \expandafter\xdef\csname pytx@pygopt@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify Pygments options for a non-existent family}{}% + \fi + }% +} +\@onlypreamble\setpythontexpygopt +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\setpythontexprettyprinter} +% We need to be able to reset the pretty printer used by a family among the options |auto|, |fancyvrb|, and |pygments|. +% \begin{macrocode} +\newcommand{\setpythontexprettyprinter}[2][]{% + \Depythontex{cmd:setpythontexprettyprinter:om:n}% + \ifstrempty{#1}{% + \ifstrequal{#2}{fancyvrb}{\boolfalse{pytx@opt@pygments}}% + \ifstrequal{#2}{pygments}{\booltrue{pytx@opt@pygments}}% + }{% + \ifcsname pytx@macroformatter@#1\endcsname + \ifbool{pytx@opt@depythontex}{}{% + \expandafter\xdef\csname pytx@macroformatter@#1\endcsname{#2}} + \else + \PackageError{\pytx@packagename}% + {Cannot modify a family that does not exist or does not allow formatter choices}% + {Create the family with \string\makepythontexfamily}% + \fi + }% +} +\@onlypreamble\setpythontexprettyprinter +% \end{macrocode} +% \end{macro} +% +% +% \subsection{Default commands and environment families} +% We are finally prepared to create the default command and environment families. We create a basic Python family with the base name |py|. We also create customized Python families for the SymPy package, using the base name |sympy|, and for the pylab module, using the base name |pylab|. All of these are created with a |console| environment. +% +% All of these command and environment families are created conditionally, depending on whether the package option |pygments| is used, via |\makepythontexfamily|. We recommend that any custom families created by the user be constructed in the same manner. +% \begin{macrocode} +\makepythontexfamily[pyglexer=python]{py} +\makepythontexfamily[pyglexer=pycon, console=true]{pycon} +\makepythontexfamily[pyglexer=python]{sympy} +\makepythontexfamily[pyglexer=pycon, console=true]{sympycon} +\makepythontexfamily[pyglexer=python]{pylab} +\makepythontexfamily[pyglexer=pycon, console=true]{pylabcon} +% \end{macrocode} +% We also need to create any additional families specified via the |usefamily| package option.\footnote{The loop here is accomplished via \texttt{etoolbox}. \texttt{pgffor} might be an alternative, but making definitions global requires trickery.} +% \begin{macrocode} +\renewcommand{\do}[1]{% + \ifstrequal{#1}{ruby}{\makepythontexfamily[pyglexer=ruby]{ruby}}{}% + \ifstrequal{#1}{rb}{\makepythontexfamily[pyglexer=ruby]{rb}}{}% + \ifstrequal{#1}{julia}{\makepythontexfamily[pyglexer=julia]{julia}}{}% + \ifstrequal{#1}{jl}{\makepythontexfamily[pyglexer=julia]{jl}}{}% +} +\expandafter\docsvlist\expandafter{\pytx@families} +% \end{macrocode} +% +% +% +% \subsection{Listings environment} +% |fancyvrb|, especially when combined with Pygments, provides most of the formatting options we could want. However, it simply typesets code within the flow of the document and does not provide a floating environment. So we create a floating environment for code listings via the |newfloat| package. +% +% It is most logical to name this environment |listing|, but that is already defined by the |minted| package (although \pytex\ and |minted| are probably not likely to be used together, due to overlapping features). Furthermore, the |listings| package specifically avoided using the name |listing| for an environment due to the use of this name by other packages. +% +% We have chosen to make a compromise. We create a macro that creates a float environment with a custom name for listings. If this macro is invoked, then a float environment for listings is created and nothing else is done. If it is not invoked, the package attempts to create an environment called |listing| at the beginning of the document, and issues a warning if another macro with that name already exists. This approach makes the logical |listing| name available in most cases, and provides the user with a simple fallback in the event that another package defining |listing| must be used alongside \pytex. +% +% \begin{macro}{\setpythontexlistingenv} +% We define a bool |pytx@listingenv| that keeps track of whether a listings environment has been created. Then we define a macro that creates a floating environment with a custom name, with appropriate settings for a listing environment. We only allow this macro to be used in the preamble, since later use would wreak havok. +% \begin{macrocode} +\newbool{pytx@listingenv} +\def\setpythontexlistingenv#1{% + \Depythontex{cmd:setpythontexlistingenv:m:n}% + \DeclareFloatingEnvironment[fileext=lopytx,listname={List of Listings},name=Listing]{#1} + \booltrue{pytx@listingenv} +} +\@onlypreamble\setpythontexlistingenv +% \end{macrocode} +% \end{macro} +% +% At the beginning of the document, we issue a warning if the |listing| environment needs to be created but cannot be due to a pre-existing macro (and no version with a custom name has been created). Otherwise, we create the |listing| environment. +% \begin{macrocode} +\AtBeginDocument{ + \ifcsname listing\endcsname + \ifbool{pytx@listingenv}{}% + {\PackageWarning{\pytx@packagename}% + {A "listing" environment already exists \MessageBreak + \pytx@packagename\space will not create one \MessageBreak + Use \string\setpythontexlistingenv to create a custom listing environment}}% + \else + \ifbool{pytx@listingenv}{}{\DeclareFloatingEnvironment[fileext=lopytx]{listing}} + \fi +} +% \end{macrocode} +% +% \subsection{Pygments for general code typesetting} +% After all the work that has gone into \pytex\ thus far, it would be a pity not to slightly expand the system to allow Pygments typesetting of any language Pygments supports. While \pytex\ currently can only \textit{execute} Python code, it is relatively easy to add support for \textit{highlighting} any language supported by Pygments. We proceed to create a |\pygment| command, a |pygments| environment, and an |\inputpygments| command that do just this. The functionality of these is very similar to that provided by the |minted| package. +% +% Both the commands and the environment are created in two forms: one that actually uses Pygments, which is the whole point in the first place; and one that uses |fancyvrb|, which may speed compilation or make editing faster since |pythontex.py| need not be invoked. By default, the two forms are switched between based on the package |pygments| option, but this may be easily modified as described below. +% +% The Pygments commands and environment operate under the code type |PYG|\meta{lexer~name}. This allows Pygments typesetting of general code to proceed with very few additions to |pythontex.py|; in most situations, the Pygments code types behave just like standard \pytex\ types that don't execute any code. Due to the use of the |PYG| prefix for all Pygments content, the use of this prefix is not allowed at the beginning of a base name for standard \pytex\ command and environment families. +% +% We have previously used the suffix |Pyg| to denote macro variants that use Pygments rather than |fancyvrb|. We continue that practice here. To distinguish the special Pygments typesetting macros from the regular \pytex\ macros, we use |Pygments| in the macro names, in addition to any |Pyg| suffix +% +% +% \subsection{Pygments utilities macros} +% \begin{macro}{\pytx@CheckPygmentsInit} +% We need to see if macros exist for storing Pygments |fv| settings and |pygopt|. If not, create them, and make sure they will be written to file. +% \begin{macrocode} +\def\pytx@CheckPygmentsInit#1{% + \ifcsname pytx@fvsettings@PYG#1\endcsname\else + \expandafter\gdef\csname pytx@fvsettings@PYG#1\endcsname{}% + \expandafter\gdef\csname pytx@pygopt@PYG#1\endcsname{}% + \AtEndDocument{\immediate\write\pytx@codefile{pygfamily=PYG#1|#1|% + \csname pytx@pygopt@PYG#1\endcsname}}% + \fi +} +% \end{macrocode} +% \end{macro} +% +% \subsubsection{Inline Pygments command} +% +% \begin{macro}{\pytx@MakePygmentsInlineFV} +% \begin{macro}{\pytx@MakePygmentsInlinePyg} +% \begin{macro}{\pygment} +% These macros create an inline command. They reuse the |\pytx@Inline| macro sequence. The approach is very similar to the constructors for inline commands, except for the way in which the type is defined and for the fact that we have to check to see if a macro for |fancyvrb| settings exists. Just as for the \pytex\ inline commands, we do not currently support |fancyvrb| options in Pygments inline commands, since almost all options are impractical for inline usage, and the few that might conceivably be practical, such as showing spaces, should probably be used throughout an entire document rather than just for a tiny code snippet within a paragraph. +% +% We supply an empty optional argument to |\pytx@Inline|, so that the |\pygment| command can only take two mandatory arguments, and no optional argument (since sessions don't make sense for code that is merely typeset): +% \begin{quote} +% |\pygment|\marg{lexer}\marg{code} +% \end{quote} +% \begin{macrocode} +\def\pytx@MakePygmentsInlineFV{% + \newcommand{\pygment}[1]{% + \edef\pytx@lexer{##1}% + \Depythontex{cmd:pygment:mv:c}% + \edef\pytx@type{PYG##1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowFV + \let\pytx@InlineSave\@empty + \let\pytx@InlinePrint\@empty + \pytx@CheckPygmentsInit{##1}% + \pytx@Inline[]% + }% +} +\def\pytx@MakePygmentsInlinePyg{% + \newcommand{\pygment}[1]{% + \edef\pytx@type{PYG##1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowPyg + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@CheckPygmentsInit{##1}% + \pytx@Inline[] + }% +} +% \end{macrocode} +% \end{macro}\end{macro}\end{macro} +% +% +% \subsubsection{Pygments environment} +% +% \begin{macro}{\pytx@MakePygmentsEnvFV}\begin{macro}{pygments} +% The |pygments| environment is created to take an optional argument, which corresponds to |fancyvrb| settings, and one mandatory argument, which corresponds to the Pygments lexer to be used in highlighting the code. +% +% The |pygments| environment begins by declaring that it is a |Verbatim| environment and setting variables. Again, some variables are unnecessary, but they are created to maintain uniformity with other \pytex\ environments. The environment code is very similar to that of \pytex\ |verb| environments. +% +% \begin{macrocode} +\def\pytx@MakePygmentsEnvFV{% + \newenvironment{pygments}{% + \VerbatimEnvironment + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BEPygmentsFV}{\endgroup\pytx@BEPygmentsFV[]}% + }% + {\end{Verbatim}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + }% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% \begin{macro}{\pytx@BEPygmentsFV} +% This macro captures the optional argument containing |fancyvrb| commands. +% \begin{macrocode} +\def\pytx@BEPygmentsFV[#1]{% + \def\pytx@fvopttmp{#1}% + \begingroup + \obeylines + \pytx@BEPygmentsFV@i +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@BEPygmentsFV@i} +% This macro captures the mandatory argument, containing the lexer name, and proceeds. +% \begin{macrocode} +\def\pytx@BEPygmentsFV@i#1{% + \endgroup + \edef\pytx@type{PYG#1}% + \edef\pytx@lexer{#1}% + \Depythontex{env:pygments:om:c}% + \DepyListing + \edef\pytx@cmd{verbatim}% + \edef\pytx@session{default}% + \edef\pytx@linecount{pytx@\pytx@type @\pytx@session @\pytx@group @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@CheckPygmentsInit{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \begin{Verbatim}% +} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\pytx@MakePygmentsEnvPyg}\begin{macro}{pygments} +% The Pygments version is very similar, except that it must bring in external Pygments content. +% \begin{macrocode} +\def\pytx@MakePygmentsEnvPyg{% + \newenvironment{pygments}{% + \VerbatimEnvironment + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BEPygmentsPyg}{\endgroup\pytx@BEPygmentsPyg[]}% + }% + {\end{VerbatimOut}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/% + \pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% \begin{macro}{\pytx@BEPygmentsPyg} +% This macro captures the optional argument, which corresponds to |fancyvrb| settings. +% \begin{macrocode} +\def\pytx@BEPygmentsPyg[#1]{% + \def\pytx@fvopttmp{#1}% + \begingroup + \obeylines + \pytx@BEPygmentsPyg@i +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\pytx@BEPygmentsPyg@i} +% This macro captures the mandatory argument, containing the lexer name, and proceeds. +% \begin{macrocode} +\def\pytx@BEPygmentsPyg@i#1{% + \endgroup + \edef\pytx@type{PYG#1}% + \edef\pytx@cmd{verbatim}% + \edef\pytx@session{default}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@WriteCodefileInfo + \pytx@CheckPygmentsInit{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +} +% \end{macrocode} +% \end{macro} +% +% +% \subsubsection{Special Pygments commands} +% +% Code highlighting may be used for some tasks that would never appear in a code execution context, which is what the \pytex\ part of this package focuses on. We create some special Pygments macros to handle these highlighting cases. +% +% \begin{macro}{\pytx@MakePygmentsInputFV} +% \begin{macro}{\pytx@MakePygmentsInputPyg} +% For completeness, we need to be able to read in a file and highlight it. This is done through some trickery with the current system. We define the type as |PYG|\meta{lexer}, and the command as |verb|. We set the context for consistency. We set the session as |EXT:|\meta{file~name}.\footnote{There is no possibility of this session being confused with a user-defined session, because colons are substituted for hyphens in all user-defined sessions, before they are written to the code file.} Next we define a |fancyvrb| settings macro for the type if it does not already exist. We write info to the code file using |\pytx@WriteCodefileInfoExt|, which writes the standard info to the code file but uses zero for the instance, since external files that are not executed can only have one instance. +% +% Then we check to see if the file actually exists, and issue a warning if not. This saves the user from running |pythontex.py| to get the same error. We perform our typical |FancyVerbLine| trickery. Next we make use of the saved content in the same way as the |pygments| environment. Note that we do not create a counter for the line numbers. This is because under typical usage an external file should have its lines numbered beginning with 1. We also encourage this by setting |firstnumber=auto| before bringing in the content. +% +% The current naming of the macro in which the Pygments content is saved is probably excessive. In almost every situation, a unique name could be formed with less information. The current approach has been taken to maintain parallelism, thus simplifying |pythontex.py|, and to avoid any rare potential conflicts. +% +% \begin{macrocode} +\def\pytx@MakePygmentsInputFV{ + \newcommand{\inputpygments}[3][]{% + \edef\pytx@lexer{##2}% + \Depythontex{cmd:inputpygments:omm:c}% + \edef\pytx@type{PYG##2}% + \edef\pytx@cmd{verbatim}% + \pytx@SetContext + \pytx@SetGroupVerb + \edef\pytx@session{EXT:##3}% + \pytx@CheckPygmentsInit{##2}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \begingroup + \DepyListing %Always must be in a group + \pytx@FVSet + \fvset{firstnumber=auto}% + \IfFileExists{##3}% + {\DepyFile{c:##3:mode=verbatim}\VerbatimInput[##1]{##3}}% + {\PackageWarning{\pytx@packagename}{Input file <##3> doesn't exist}}% + \endgroup + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + }% +} +\def\pytx@MakePygmentsInputPyg{ + \newcommand{\inputpygments}[3][]{% + \edef\pytx@type{PYG##2}% + \edef\pytx@cmd{verbatim}% + \pytx@SetContext + \pytx@SetGroupVerb + \edef\pytx@session{EXT:##3}% + \pytx@CheckPygmentsInit{##2}% + \pytx@WriteCodefileInfoExt + \IfFileExists{##3}{}{\PackageWarning{\pytx@packagename}% + {Input file <##3> does not exist}} + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \begingroup + \pytx@FVSet + \fvset{firstnumber=auto}% + \ifcsname FV@SV@pytx@\pytx@type @\pytx@session @\pytx@group @0\endcsname + \UseVerbatim[##1]{pytx@\pytx@type @\pytx@session @\pytx@group @0}% + \else + \InputIfFileExists{\pytx@outputdir/##3_##2.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \endgroup + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + }% +} +% \end{macrocode} +% \end{macro}\end{macro} +% +% +% +% +% \subsubsection{Creating the Pygments commands and environment} +% +% We are almost ready to actually create the Pygments commands and environments. First, though, we create some macros that allow the user to set |fancyvrb| settings, Pygments options, and formatting of Pygments content. +% \begin{macro}{\setpygmentsfv} +% This macro allows |fancyvrb| settings to be specified for a Pygments lexer. It takes the lexer name as the optional argument and the settings as the mandatory argument. If no optional argument (lexer) is supplied, then it sets the document-wide |fancyvrb| settings, and is in that case equivalent to |\setpythontexfv|. +% \begin{macrocode} +\newcommand{\setpygmentsfv}[2][]{% + \Depythontex{cmd:setpygmentsfv:om:n}% + \ifstrempty{#1}% + {\gdef\pytx@fvsettings{#2}}% + {\expandafter\gdef\csname pytx@fvsettings@PYG#1\endcsname{#2}}% +}% +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\setpygmentspygopt} +% This macro allows the Pygments option to be set for a lexer. It takes the lexer name as the first argument and the options as the second argument. If this macro is used multiple times for a lexer, it will write the settings to the code file multiple times. But |pythontex.py| will simply process all settings, and each subsequent set of settings will overwrite any prior settings, so this is not a problem. +% \begin{macrocode} +\newcommand{\setpygmentspygopt}[2][]{% + \Depythontex{cmd:setpygmentspygopt:om:n}% + \ifstrempty{#1}{\def\pytx@pygopt{#2}}{% + \expandafter\gdef\csname pytx@pygopt@PYG#1\endcsname{#2}}% +} +\@onlypreamble\setpygmentspygopt +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\setpygmentsprettyprinter} +% This macro parallels |\setpythontexprettyprinter|. Currently, it takes no optional argument. Eventually, it may be desirable to allow an optional argument that sets the pretty printer on a per-lexer basis. +% \begin{macrocode} +\newcommand{\setpygmentsprettyprinter}[1]{% + \Depythontex{cmd:setpygmentsprettyprinter:m:n}% + \ifstrequal{#1}{fancyvrb}{\boolfalse{pytx@opt@pygments}}% + \ifstrequal{#1}{pygments}{\booltrue{pytx@opt@pygments}}% +} +\@onlypreamble\setpygmentsprettyprinter +\xdef\pytx@macroformatter@PYG{auto} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\makepygmentsfv} +% This macro creates the Pygments commands and environment using |fancyvrb|, as a fallback when Pygments is unavailable or when the user desires maximum speed. +% \begin{macrocode} +\def\makepygmentsfv{% + \pytx@MakePygmentsInlineFV + \pytx@MakePygmentsEnvFV + \pytx@MakePygmentsInputFV +}% +\@onlypreamble\makepygmentsfv +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\makepygmentspyg} +% This macro creates the Pygments commands and environment using Pygments. We must set the bool |pytx@usedpygments| true so that |pythontex.py| knows that Pygments content is present and must be highlighted. +% \begin{macrocode} +\def\makepygmentspyg{% + \ifbool{pytx@opt@pyginline}% + {\pytx@MakePygmentsInlinePyg}% + {\pytx@MakePygmentsInlineFV}% + \pytx@MakePygmentsEnvPyg + \pytx@MakePygmentsInputPyg + \booltrue{pytx@usedpygments} +}% +\@onlypreamble\makepygmentspyg +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}{\makepygments} +% This macro uses the two preceding macros to conditionally define the Pygments commands and environments, based on the package Pygments settings. +% \begin{macrocode} +\def\makepygments{% + \AtBeginDocument{% + \ifdefstring{\pytx@macroformatter@PYG}{auto}% + {\ifbool{pytx@opt@pygments}% + {\makepygmentspyg}{\makepygmentsfv}}{} + \ifdefstring{\pytx@macroformatter@PYG}{pygments}% + {\makepygmentspyg}{} + \ifdefstring{\pytx@macroformatter@PYG}{fancyvrb}% + {\makepygmentsfv}{} + }% +}% +\@onlypreamble\makepygments +% \end{macrocode} +% \end{macro} +% +% We conclude by actually creating the Pygments commands and environments. +% \begin{macrocode} +\makepygments +% \end{macrocode} +% +% +% \subsection{Final cleanup} +% At the end of the document, we need to close files. +% \begin{macrocode} +\AfterEndDocument{% + \immediate\closeout\pytx@codefile + \ifbool{pytx@opt@depythontex}{\immediate\closeout\pytx@depyfile}{}% +} +% \end{macrocode} +% +% +% \subsection{Compatibility with beta releases} +% +% The following code maintains compatibility with the beta releases when the package option |beta| is used. It will be retained for several releases before being removed. +% +% \begin{macrocode} +\ifbool{pytx@opt@beta}{ + +% Revert changes in stdout and stderr modes +\def\pytx@FetchStdoutfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stdout}{% + \ifstrempty{#1}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{verb}{\VerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{inlineverb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{v}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \DepyFile{p:\pytx@outputdir/#3.stdout:mode=#1}% + }% + {\pytx@stdout@warntext + \PackageWarning{\pytx@packagename}{Non-existent printed content}}% +} +\def\pytx@FetchStderrfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stderr}{% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stderr}}{}% + \ifstrempty{#1}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{verb}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{inlineverb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{v}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \DepyFile{p:\pytx@outputdir/#3.stderr:mode=#1}% + }% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent stderr content}}% +} + + +% Old verb environment +\renewcommand{\pytx@MakeVerbFV}[1]{% + \expandafter\newenvironment{#1verb}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1verb:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{verb}% + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginVerbEnvFV}{\endgroup\pytx@BeginVerbEnvFV[]}% + }% + {\end{Verbatim}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\renewcommand{\pytx@MakePygEnv}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \xdef\pytx@type{#1}% + \edef\pytx@cmd{#2}% + \pytx@SetContext + \ifstrequal{#2}{block}{\pytx@SetGroup}{} + \ifstrequal{#2}{verb}{\pytx@SetGroupVerb}{} + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginEnvPyg}{\endgroup\pytx@BeginEnvPyg[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +}% +\renewcommand{\pytx@MakeVerbPyg}[1]{\pytx@MakePygEnv{#1}{verb}} + + +% Settings macros +\def\setpythontexpyglexer#1#2{% + \Depythontex{cmd:setpythontexpyglexer:mm:n}% + \ifcsname pytx@pyglexer@#1\endcsname + \expandafter\xdef\csname pytx@pyglexer@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify a non-existent family}{}% + \fi +}% +\renewcommand{\setpythontexpygopt}[2]{% + \Depythontex{cmd:setpythontexpygopt:mm:n}% + \ifcsname pytx@pygopt@#1\endcsname + \expandafter\xdef\csname pytx@pygopt@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify Pygments options for a non-existent family}{}% + \fi +} +\def\setpygmentspygopt#1#2{% + \Depythontex{cmd:setpygmentspygopt:mm:n}% + \AtEndDocument{\immediate\write\pytx@codefile{% + \pytx@delimsettings pygfamily=PYG#1,#1,% + \string{#2\string}\pytx@delimchar}% + }% +} + + +% Old formatters +\def\setpythontexformatter#1#2{% + \Depythontex{cmd:setpythontexformatter:mm:n}% + \ifcsname pytx@macroformatter@#1\endcsname + \ifbool{pytx@opt@depythontex}{}{% + \expandafter\xdef\csname pytx@macroformatter@#1\endcsname{#2}} + \else + \PackageError{\pytx@packagename}% + {Cannot modify a family that does not exist or does not allow formatter choices}% + {Create the family with \string\makepythontexfamily}% + \fi +} +\@onlypreamble\setpythontexformatter +\def\setpygmentsformatter#1{% + \Depythontex{cmd:setpygmentsformatter:m:n}% + \ifbool{pytx@opt@depythontex}{}{\xdef\pytx@macroformatter@PYG{#1}}} +\@onlypreamble\setpygmentsformatter + + + + +}{} %End beta +% \end{macrocode} +% +% +% +% \iffalse +%</package> +% \fi +%% \Finale +\endinput diff --git a/Master/texmf-dist/source/latex/pythontex/pythontex.ins b/Master/texmf-dist/source/latex/pythontex/pythontex.ins new file mode 100644 index 00000000000..7eb253091a8 --- /dev/null +++ b/Master/texmf-dist/source/latex/pythontex/pythontex.ins @@ -0,0 +1,57 @@ +%% Copyright (C) 2012-2013 by Geoffrey M. Poore <gpoore@gmail.com> +%% -------------------------------------------------------------------------- +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3 +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3 or later is part of all distributions of LaTeX +%% version 2005/12/01 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% +%% The Current Maintainer of this work is Geoffrey M. Poore. +%% +%% This work consists of the files pythontex.dtx and pythontex.ins +%% and the derived filebase pythontex.sty. +%% + +\input docstrip.tex +\keepsilent + +\usedir{tex/latex/pythontex} + +\preamble + +This is a generated file. + +Copyright (C) 2012-2013 by Geoffrey M. Poore <gpoore@gmail.com> +-------------------------------------------------------------------------- +This work may be distributed and/or modified under the +conditions of the LaTeX Project Public License, either version 1.3 +of this license or (at your option) any later version. +The latest version of this license is in + http://www.latex-project.org/lppl.txt +and version 1.3 or later is part of all distributions of LaTeX +version 2005/12/01 or later. + +\endpreamble + +\generate{\file{pythontex.sty}{\from{pythontex.dtx}{package}}} + +\obeyspaces +\Msg{*************************************************************} +\Msg{* *} +\Msg{* To finish the installation you have to move the following *} +\Msg{* file into a directory searched by TeX: *} +\Msg{* *} +\Msg{* pythontex.sty *} +\Msg{* *} +\Msg{* To produce the documentation run the file pythontex.dtx *} +\Msg{* through LaTeX. *} +\Msg{* *} +\Msg{* Happy TeXing! *} +\Msg{* *} +\Msg{*************************************************************} + +\endbatchfile diff --git a/Master/texmf-dist/tex/latex/pythontex/pythontex.sty b/Master/texmf-dist/tex/latex/pythontex/pythontex.sty new file mode 100644 index 00000000000..a71b4f48d2d --- /dev/null +++ b/Master/texmf-dist/tex/latex/pythontex/pythontex.sty @@ -0,0 +1,1595 @@ +%% +%% This is file `pythontex.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% pythontex.dtx (with options: `package') +%% +%% This is a generated file. +%% +%% Copyright (C) 2012-2013 by Geoffrey M. Poore <gpoore@gmail.com> +%% -------------------------------------------------------------------------- +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3 +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3 or later is part of all distributions of LaTeX +%% version 2005/12/01 or later. +%% +\NeedsTeXFormat{LaTeX2e}[1999/12/01] +\ProvidesPackage{pythontex} + [2013/08/26 Version~0.12] + +\newcommand{\pytx@packagename}{PythonTeX} +\newcommand{\pytx@packageversion}{v0.12} +\RequirePackage{fancyvrb} +\RequirePackage{etex} +\RequirePackage{etoolbox} +\RequirePackage{xstring} +\RequirePackage{pgfopts} +\RequirePackage{newfloat} +\RequirePackage{currfile} +\AtBeginDocument{\@ifpackageloaded{color}{}{\RequirePackage{xcolor}}} +\def\pytx@families{} +\pgfkeys{/PYTX/pkgopt/usefamily/.estore in=\pytx@families} +\def\pytx@opt@gobble{none} +\pgfkeys{/PYTX/pkgopt/gobble/.is choice} +\pgfkeys{/PYTX/pkgopt/gobble/none/.code=\def\pytx@opt@gobble{none}} +\pgfkeys{/PYTX/pkgopt/gobble/auto/.code=\def\pytx@opt@gobble{auto}} +\newbool{pytx@opt@beta} +\pgfkeys{/PYTX/pkgopt/beta/.default=true} +\pgfkeys{/PYTX/pkgopt/beta/.is choice} +\pgfkeys{/PYTX/pkgopt/beta/true/.code=\booltrue{pytx@opt@beta}} +\pgfkeys{/PYTX/pkgopt/beta/false/.code=\boolfalse{pytx@opt@beta}} +\def\pytx@opt@rerun{default} +\pgfkeys{/PYTX/pkgopt/runall/.default=true} +\pgfkeys{/PYTX/pkgopt/runall/.is choice} +\pgfkeys{/PYTX/pkgopt/runall/true/.code=\def\pytx@opt@rerun{always}} +\pgfkeys{/PYTX/pkgopt/runall/false/.code=\relax} +\pgfkeys{/PYTX/pkgopt/rerun/.is choice} +\pgfkeys{/PYTX/pkgopt/rerun/never/.code=\def\pytx@opt@rerun{never}} +\pgfkeys{/PYTX/pkgopt/rerun/modified/.code=\def\pytx@opt@rerun{modified}} +\pgfkeys{/PYTX/pkgopt/rerun/errors/.code=\def\pytx@opt@rerun{errors}} +\pgfkeys{/PYTX/pkgopt/rerun/warnings/.code=\def\pytx@opt@rerun{warnings}} +\pgfkeys{/PYTX/pkgopt/rerun/always/.code=\def\pytx@opt@rerun{always}} +\pgfkeys{/PYTX/pkgopt/rerun/all/.code=\def\pytx@opt@rerun{always}% + \PackageWarning{\pytx@packagename}{rerun=all is deprecated; use rerun=always}} +\def\pytx@opt@hashdependencies{default} +\pgfkeys{/PYTX/pkgopt/hashdependencies/.is choice} +\pgfkeys{/PYTX/pkgopt/hashdependencies/.default=true} +\pgfkeys{/PYTX/pkgopt/hashdependencies/true/.code=\def\pytx@opt@hashdependencies{true}} +\pgfkeys{/PYTX/pkgopt/hashdependencies/false/.code=\def\pytx@opt@hashdependencies{false}} +\newbool{pytx@opt@autoprint} +\booltrue{pytx@opt@autoprint} +\pgfkeys{/PYTX/pkgopt/autoprint/.default=true} +\pgfkeys{/PYTX/pkgopt/autoprint/.is choice} +\pgfkeys{/PYTX/pkgopt/autoprint/true/.code=\booltrue{pytx@opt@autoprint}} +\pgfkeys{/PYTX/pkgopt/autoprint/false/.code=\boolfalse{pytx@opt@autoprint}} +\pgfkeys{/PYTX/pkgopt/autostdout/.default=true} +\pgfkeys{/PYTX/pkgopt/autostdout/.is choice} +\pgfkeys{/PYTX/pkgopt/autostdout/true/.code=\booltrue{pytx@opt@autoprint}} +\pgfkeys{/PYTX/pkgopt/autostdout/false/.code=\boolfalse{pytx@opt@autoprint}} +\newcommand{\setpythontexautoprint}[1]{% + \Depythontex{cmd:setpythontexautoprint:m:n}% + \ifstrequal{#1}{true}{\booltrue{pytx@opt@autoprint}}{}% + \ifstrequal{#1}{false}{\boolfalse{pytx@opt@autoprint}}{}% +} +\newcommand{\setpythontexautostdout}[1]{% + \Depythontex{cmd:setpythontexautostdout:m:n}% + \ifstrequal{#1}{true}{\booltrue{pytx@opt@autoprint}}{}% + \ifstrequal{#1}{false}{\boolfalse{pytx@opt@autoprint}}{}% +} +\newbool{pytx@opt@stdout} +\booltrue{pytx@opt@stdout} +\pgfkeys{/PYTX/pkgopt/debug/.code=\boolfalse{pytx@opt@stdout}} +\pgfkeys{/PYTX/pkgopt/stdout/.default=true} +\pgfkeys{/PYTX/pkgopt/stdout/.is choice} +\pgfkeys{/PYTX/pkgopt/stdout/true/.code=\booltrue{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option stdout is deprecated; use option debug}} +\pgfkeys{/PYTX/pkgopt/stdout/false/.code=\boolfalse{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option stdout is deprecated; use option debug}} +\pgfkeys{/PYTX/pkgopt/print/.default=true} +\pgfkeys{/PYTX/pkgopt/print/.is choice} +\pgfkeys{/PYTX/pkgopt/print/true/.code=\booltrue{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option print is deprecated; use option debug}} +\pgfkeys{/PYTX/pkgopt/print/false/.code=\boolfalse{pytx@opt@stdout}% + \PackageWarning{\pytx@packagename}{Option print is deprecated; use option debug}} +\AtBeginDocument{% + \ifbool{pytx@opt@stdout}{}{% + \PackageWarning{\pytx@packagename}{Using package option debug}% + }% +} +\newbool{pytx@opt@stderr} +\pgfkeys{/PYTX/pkgopt/makestderr/.default=true} +\pgfkeys{/PYTX/pkgopt/makestderr/.is choice} +\pgfkeys{/PYTX/pkgopt/makestderr/true/.code=\booltrue{pytx@opt@stderr}} +\pgfkeys{/PYTX/pkgopt/makestderr/false/.code=\boolfalse{pytx@opt@stderr}} +\pgfkeys{/PYTX/pkgopt/stderr/.default=true} +\pgfkeys{/PYTX/pkgopt/stderr/.is choice} +\pgfkeys{/PYTX/pkgopt/stderr/true/.code=\booltrue{pytx@opt@stderr}% + \PackageWarning{\pytx@packagename}{Option stderr is deprecated; use option makestderr}} +\pgfkeys{/PYTX/pkgopt/stderr/false/.code=\boolfalse{pytx@opt@stderr}% + \PackageWarning{\pytx@packagename}{Option stderr is deprecated; use option makestderr}} + +\def\pytx@opt@stderrfilename{full} +\pgfkeys{/PYTX/pkgopt/stderrfilename/.default=full} +\pgfkeys{/PYTX/pkgopt/stderrfilename/.is choice} +\pgfkeys{/PYTX/pkgopt/stderrfilename/full/.code=\def\pytx@opt@stderrfilename{full}} +\pgfkeys{/PYTX/pkgopt/stderrfilename/session/.code=\def\pytx@opt@stderrfilename{session}} +\pgfkeys{/PYTX/pkgopt/stderrfilename/genericfile/.code=% + \def\pytx@opt@stderrfilename{genericfile}} +\pgfkeys{/PYTX/pkgopt/stderrfilename/genericscript/.code=% + \def\pytx@opt@stderrfilename{genericscript}} +\def\pytx@opt@pyfuture{default} +\pgfkeys{/PYTX/pkgopt/pyfuture/.is choice} +\pgfkeys{/PYTX/pkgopt/pyfuture/default/.code=\def\pytx@opt@pyfuture{default}} +\pgfkeys{/PYTX/pkgopt/pyfuture/all/.code=\def\pytx@opt@pyfuture{all}} +\pgfkeys{/PYTX/pkgopt/pyfuture/none/.code=\def\pytx@opt@pyfuture{none}} +\def\pytx@opt@pyconfuture{none} +\pgfkeys{/PYTX/pkgopt/pyconfuture/.is choice} +\pgfkeys{/PYTX/pkgopt/pyconfuture/default/.code=\def\pytx@opt@pyconfuture{default}} +\pgfkeys{/PYTX/pkgopt/pyconfuture/all/.code=\def\pytx@opt@pyconfuture{all}} +\pgfkeys{/PYTX/pkgopt/pyconfuture/none/.code=\def\pytx@opt@pyconfuture{none}} +\newbool{pytx@opt@upquote} +\booltrue{pytx@opt@upquote} +\pgfkeys{/PYTX/pkgopt/upquote/.default=true} +\pgfkeys{/PYTX/pkgopt/upquote/.is choice} +\pgfkeys{/PYTX/pkgopt/upquote/true/.code=\booltrue{pytx@opt@upquote}} +\pgfkeys{/PYTX/pkgopt/upquote/false/.code=\boolfalse{pytx@opt@upquote}} +\newbool{pytx@opt@fixlr} +\pgfkeys{/PYTX/pkgopt/fixlr/.default=true} +\pgfkeys{/PYTX/pkgopt/fixlr/.is choice} +\pgfkeys{/PYTX/pkgopt/fixlr/true/.code=\booltrue{pytx@opt@fixlr}} +\pgfkeys{/PYTX/pkgopt/fixlr/false/.code=\boolfalse{pytx@opt@fixlr}} +\def\pytx@opt@keeptemps{none} +\pgfkeys{/PYTX/pkgopt/keeptemps/.default=all} +\pgfkeys{/PYTX/pkgopt/keeptemps/.is choice} +\pgfkeys{/PYTX/pkgopt/keeptemps/all/.code=\def\pytx@opt@keeptemps{all}} +\pgfkeys{/PYTX/pkgopt/keeptemps/code/.code=\def\pytx@opt@keeptemps{code}} +\pgfkeys{/PYTX/pkgopt/keeptemps/none/.code=\def\pytx@opt@keeptemps{none}} +\newbool{pytx@opt@pygments} +\booltrue{pytx@opt@pygments} +\pgfkeys{/PYTX/pkgopt/prettyprinter/.is choice} +\pgfkeys{/PYTX/pkgopt/prettyprinter/pygments/.code=\booltrue{pytx@opt@pygments}} +\pgfkeys{/PYTX/pkgopt/prettyprinter/fancyvrb/.code=\boolfalse{pytx@opt@pygments}} +\pgfkeys{/PYTX/pkgopt/pygments/.default=true} +\pgfkeys{/PYTX/pkgopt/pygments/.is choice} +\pgfkeys{/PYTX/pkgopt/pygments/true/.code=\booltrue{pytx@opt@pygments}} +\pgfkeys{/PYTX/pkgopt/pygments/false/.code=\boolfalse{pytx@opt@pygments}} +\newbool{pytx@opt@pyginline} +\booltrue{pytx@opt@pyginline} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/.default=true} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/.is choice} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/true/.code=\booltrue{pytx@opt@pyginline}} +\pgfkeys{/PYTX/pkgopt/prettyprintinline/false/.code=\boolfalse{pytx@opt@pyginline}} +\pgfkeys{/PYTX/pkgopt/pyginline/.default=true} +\pgfkeys{/PYTX/pkgopt/pyginline/.is choice} +\pgfkeys{/PYTX/pkgopt/pyginline/true/.code=\booltrue{pytx@opt@pyginline}} +\pgfkeys{/PYTX/pkgopt/pyginline/false/.code=\boolfalse{pytx@opt@pyginline}} +\def\pytx@pyglexer{} +\pgfkeys{/PYTX/pkgopt/pyglexer/.code=\def\pytx@pyglexer{#1}} +\def\pytx@pygopt{} +\pgfkeys{/PYTX/pkgopt/pygopt/.code=\def\pytx@pygopt{#1}} +\def\pytx@fvextfile{-1} +\pgfkeys{/PYTX/pkgopt/fvextfile/.default=25} +\pgfkeys{/PYTX/pkgopt/fvextfile/.code=\IfInteger{#1}{% + \ifnum#1>0\relax + \def\pytx@fvextfile{#1}% + \else + \PackageError{\pytx@packagename}{option fvextfile must be an integer > 0}{}% + \fi}% + {\PackageError{\pytx@packagename}{option fvextfile must be an integer > 0}{}}% +} +\def\pytx@opt@pyconbanner{none} +\pgfkeys{/PYTX/pkgopt/pyconbanner/.is choice} +\pgfkeys{/PYTX/pkgopt/pyconbanner/none/.code=\def\pytx@opt@pyconbanner{none}} +\pgfkeys{/PYTX/pkgopt/pyconbanner/standard/.code=\def\pytx@opt@pyconbanner{standard}} +\pgfkeys{/PYTX/pkgopt/pyconbanner/default/.code=\def\pytx@opt@pyconbanner{default}} +\pgfkeys{/PYTX/pkgopt/pyconbanner/pyversion/.code=\def\pytx@opt@pyconbanner{pyversion}} +\def\pytx@opt@pyconfilename{stdin} +\pgfkeys{/PYTX/pkgopt/pyconfilename/.is choice} +\pgfkeys{/PYTX/pkgopt/pyconfilename/stdin/.code=\def\pytx@opt@pyconfilename{stdin}} +\pgfkeys{/PYTX/pkgopt/pyconfilename/console/.code=\def\pytx@opt@pyconfilename{console}} +\newbool{pytx@opt@depythontex} +\pgfkeys{/PYTX/pkgopt/depythontex/.default=true} +\pgfkeys{/PYTX/pkgopt/depythontex/.is choice} +\pgfkeys{/PYTX/pkgopt/depythontex/true/.code=\booltrue{pytx@opt@depythontex}} +\pgfkeys{/PYTX/pkgopt/depythontex/false/.code=\boolfalse{pytx@opt@depythontex}} +\let\Depythontex\@gobble +\let\DepyFile\@gobble +\let\DepyMacro\@gobble +\let\DepyListing\@empty +\ProcessPgfPackageOptions{/PYTX/pkgopt} +\ifbool{pytx@opt@fixlr}{ + \IfStrEq{\detokenize\expandafter{\left}}{\detokenize{\left}}{ + \let\originalleft\left + \let\originalright\right + \renewcommand{\left}{\mathopen{}\mathclose\bgroup\originalleft} + \renewcommand{\right}{\aftergroup\egroup\originalright} + }{} +}{} +\ifbool{pytx@opt@upquote}{\RequirePackage{upquote}}{} +\ifbool{pytx@opt@depythontex}{\boolfalse{pytx@opt@pygments}}{} +\def\pytx@CheckCounter#1{% + \ifcsname c@#1\endcsname\else\newcounter{#1}\fi +} +\def\pytx@SVMCR#1{% + \edef\pytx@tmp{\csname #1\endcsname}% + \begingroup + \endlinechar`\^^J + \let\do\@makeother\dospecials + \pytx@SVMCR@i} +\begingroup +\catcode`!=0 +!catcode`!\=12 +!long!gdef!pytx@SVMCR@i#1\endpytx@SVMCR^^J{% + !endgroup + !expandafter!gdef!pytx@tmp{% + !expandafter!scantokens!expandafter{#1!empty}}% +}% +!endgroup +\expandafter\ifx\csname directlua\endcsname\relax\else +\def\pytx@SVMCR#1 {% + \edef\pytx@tmp{\csname #1\endcsname}% + \pytx@SVMCR@i} +\long\def\pytx@SVMCR@i#1\endpytx@SVMCR{% + \expandafter\gdef\pytx@tmp{#1}% +} +\fi +\newcommand{\definepythontexcontext}[1]{% + \def\pytx@SetContext{% + \edef\pytx@context{#1}% + }% +} +\definepythontexcontext{} +\@onlypreamble\definepythontexcontext +\newcommand{\restartpythontexsession}[1]{% + \Depythontex{cmd:restartpythontexsession:m:n}% + \def\pytx@SetGroup{% + \edef\pytx@group{#1}% + }% + \def\pytx@SetGroupVerb{% + \edef\pytx@group{#1verb}% + }% + \def\pytx@SetGroupCons{% + \edef\pytx@group{#1cons}% + }% + \AtBeginDocument{% + \pytx@SetGroup + \IfSubStr{\pytx@group}{verb}{% + \PackageError{\pytx@packagename}% + {String "verb" is not allowed in \string\restartpythontexsession}% + {Use \string\restartpythontexsession with a valid argument}}{}% + \IfSubStr{\pytx@group}{cons}{% + \PackageError{\pytx@packagename}% + {String "cons" is not allowed in \string\restartpythontexsession}% + {Use \string\restartpythontexsession with a valid argument}}{}% + }% +} +\@onlypreamble\restartpythontexsession +\restartpythontexsession{default} +\StrSubstitute{\jobname}{ }{-}[\pytx@jobname] +\StrSubstitute{\pytx@jobname}{"}{}[\pytx@jobname] +\StrSubstitute{\pytx@jobname}{*}{-}[\pytx@jobname] +\newcommand{\setpythontexoutputdir}[1]{% + \Depythontex{cmd:setpythontexoutputdir:m:n}% + \def\pytx@outputdir{#1}} +\setpythontexoutputdir{pythontex-files-\pytx@jobname} +\AtBeginDocument{% + \@ifpackageloaded{graphicx}{% + \ifx\Ginput@path\@undefined + \graphicspath{{\pytx@outputdir/}}% + \else + \g@addto@macro\Ginput@path{{\pytx@outputdir/}}% + \fi + }{}% +} +\@onlypreamble\setpythontexoutputdir +\newcommand{\setpythontexworkingdir}[1]{% + \Depythontex{cmd:setpythontexworkingdir:m:n}% + \def\pytx@workingdir{#1}% +} +\@onlypreamble\setpythontexworkingdir +\AtBeginDocument{% + \ifcsname pytx@workingdir\endcsname\else + %\setpythontexworkingdir{\pytx@outputdir}\fi Depythontex + \let\pytx@workingdir\pytx@outputdir\fi +} +\newbool{pytx@usedpygments} +\AfterEndPreamble{% + \ifbool{pytx@usedpygments}% + {\InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxpyg}{}{}}{}% +} +\AtBeginDocument{% + \makeatletter + \InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxmcr}{}{}% + \makeatother +} +\newwrite\pytx@codefile +\immediate\openout\pytx@codefile=\jobname.pytxcode +\edef\pytx@delimchar{\string#} +\edef\pytx@delim{=\string>PYTHONTEX\string#} +\edef\pytx@delimsettings{=\string>PYTHONTEX:SETTINGS\string#} +\AtEndDocument{% + \immediate\write\pytx@codefile{\pytx@delimsettings}% + \immediate\write\pytx@codefile{version=\pytx@packageversion}% + \immediate\write\pytx@codefile{outputdir=\pytx@outputdir}% + \immediate\write\pytx@codefile{workingdir=\pytx@workingdir}% + \immediate\write\pytx@codefile{gobble=\pytx@opt@gobble}% + \immediate\write\pytx@codefile{rerun=\pytx@opt@rerun}% + \immediate\write\pytx@codefile{hashdependencies=\pytx@opt@hashdependencies}% + \immediate\write\pytx@codefile{makestderr=\ifbool{pytx@opt@stderr}{true}{false}}% + \immediate\write\pytx@codefile{stderrfilename=\pytx@opt@stderrfilename}% + \immediate\write\pytx@codefile{keeptemps=\pytx@opt@keeptemps}% + \immediate\write\pytx@codefile{pyfuture=\pytx@opt@pyfuture}% + \immediate\write\pytx@codefile{pyconfuture=\pytx@opt@pyconfuture}% + \immediate\write\pytx@codefile{pygments=\ifbool{pytx@opt@pygments}{true}{false}}% + \immediate\write\pytx@codefile{pygglobal=:GLOBAL|\pytx@pyglexer|\pytx@pygopt}% + \immediate\write\pytx@codefile{fvextfile=\pytx@fvextfile}% + \immediate\write\pytx@codefile{pyconbanner=\pytx@opt@pyconbanner}% + \immediate\write\pytx@codefile{pyconfilename=\pytx@opt@pyconfilename}% + \immediate\write\pytx@codefile{depythontex=\ifbool{pytx@opt@depythontex}{true}{false}}% +} +\def\pytx@argsrun{} +\def\pytx@argspprint{} +\def\pytx@WriteCodefileInfo{% + \ifcurrfile{\currfilebase}{\jobname}% + {\let\pytx@currfile\@empty}{\let\pytx@currfile\currfilename}% + \immediate\write\pytx@codefile{\pytx@delim\pytx@type\pytx@delimchar% + \pytx@session\pytx@delimchar\pytx@group\pytx@delimchar% + \arabic{\pytx@counter}\pytx@delimchar\pytx@cmd\pytx@delimchar% + \pytx@context\pytx@delimchar\pytx@argsrun\pytx@delimchar% + \pytx@argspprint\pytx@delimchar% + \pytx@currfile\pytx@delimchar% + \the\inputlineno\pytx@delimchar}% +} +\newcommand{\pytx@WriteCodefileInfoExt}[1][]{% + \ifcurrfile{\currfilebase}{\jobname}% + {\let\pytx@currfile\@empty}{\let\pytx@currfile\currfilename}% + \immediate\write\pytx@codefile{\pytx@delim\pytx@type\pytx@delimchar% + \pytx@session\pytx@delimchar\pytx@group\pytx@delimchar% + 0\pytx@delimchar\pytx@cmd\pytx@delimchar% + \pytx@context\pytx@delimchar\pytx@argsrun\pytx@delimchar% + \pytx@argspprint\pytx@delimchar% + \pytx@currfile\pytx@delimchar% + \the\inputlineno\pytx@delimchar#1}% +} +\newcommand{\setpythontexfv}[2][]{% + \Depythontex{cmd:setpythontexfv:om:n}% + \ifstrempty{#1}% + {\gdef\pytx@fvsettings{#2}}% + {\expandafter\gdef\csname pytx@fvsettings@#1\endcsname{#2}}% +}% +\setpythontexfv{tabsize=4} +\def\pytx@FVSet{% + \expandafter\let\expandafter\pytx@fvsettings@@% + \csname pytx@fvsettings@\pytx@type\endcsname + \ifdefstring{\pytx@fvsettings@@}{}% + {}% + {\expandafter\fvset\expandafter{\pytx@fvsettings@@}}% + \ifdefstring{\pytx@fvsettings}{}% + {}% + {\expandafter\fvset\expandafter{\pytx@fvsettings}}% +} + +\newcounter{pytx@FancyVerbLineTemp} +\def\pytx@FVB@SaveVerbatim#1{% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \@bsphack + \begingroup + \FV@UseKeyValues + \def\SaveVerbatim@Name{#1}% + \def\FV@ProcessLine##1{% + \expandafter\gdef\expandafter\FV@TheVerbatim\expandafter{% + \FV@TheVerbatim\FV@StepLineNo\FV@ProcessLine{##1}}}% + \gdef\FV@TheVerbatim{\FV@CodeLineNo=1}% + \FV@Scan} +\def\pytx@FVE@SaveVerbatim{% + \expandafter\global\expandafter\let + \csname FV@SV@\SaveVerbatim@Name\endcsname\FV@TheVerbatim + \endgroup\@esphack + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}} +\let\FVB@SaveVerbatim\pytx@FVB@SaveVerbatim +\let\FVE@SaveVerbatim\pytx@FVE@SaveVerbatim +\def\pytx@stdfile{} +\def\pytx@stdout@warntext{} +\def\pytx@FetchStdoutfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stdout}{% + \ifstrempty{#1}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{verb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{verbatim}{\VerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \DepyFile{p:\pytx@outputdir/#3.stdout:mode=#1}% + }% + {\pytx@stdout@warntext + \PackageWarning{\pytx@packagename}{Non-existent printed content}}% +} +\ifbool{pytx@opt@stdout}{}{\def\pytx@FetchStdoutfile[#1][#2]#3{}} +\AtBeginDocument{\def\pytx@stdout@warntext{\textbf{??~\pytx@packagename~??}}} +\def\stdoutpythontex{% + \Depythontex{cmd:stdoutpythontex:oo:p}% + \@ifnextchar[{\pytx@Stdout}{\pytx@Stdout[raw]}% +} +\def\pytx@Stdout[#1]{% + \@ifnextchar[{\pytx@Stdout@i[#1]}{\pytx@Stdout@i[#1][]}% +} +\def\pytx@Stdout@i[#1][#2]{% + \pytx@FetchStdoutfile[#1][#2]{\pytx@stdfile}% +} +\def\printpythontex{% + \Depythontex{cmd:printpythontex:oo:p}% + \@ifnextchar[{\pytx@Stdout}{\pytx@Stdout[raw]}% +} +\def\savestdoutpythontex{% + \Depythontex{cmd:savestdoutpythontex:m:n}% + \savestdoutpythontex@i +} +\def\savestdoutpythontex@i#1{% + \ifcsname pytx@SVout@#1\endcsname + \PackageError{\pytx@packagename}% + {Attempt to save content using an already-defined name}% + {Use a name that is not already defined}% + \else + \expandafter\edef\csname pytx@SVout@#1\endcsname{\pytx@stdfile}% + \fi +} +\def\saveprintpythontex{% + \Depythontex{cmd:saveprintpythontex:m:n}% + \savestdoutpythontex@i +} +\def\usestdoutpythontex{% + \Depythontex{cmd:usestdoutpythontex:oom:p}% + \@ifnextchar[{\pytx@UseStdout}{\pytx@UseStdout[]}% +} +\def\pytx@UseStdout[#1]{% + \@ifnextchar[{\pytx@UseStdout@i[#1]}{\pytx@UseStdout@i[#1][]}% +} +\def\pytx@UseStdout@i[#1][#2]#3{% + \ifcsname pytx@SVout@#3\endcsname + \pytx@FetchStdoutfile[#1][#2]{\csname pytx@SVout@#3\endcsname}% + \else + \textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent saved printed content}% + \fi +} +\def\useprintpythontex{% + \Depythontex{cmd:useprintpythontex:oom:p}% + \@ifnextchar[{\pytx@UseStdout}{\pytx@UseStdout[]}% +} +\def\pytx@FetchStderrfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stderr}{% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stderr}}{}% + \ifstrempty{#1}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{verb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{verbatim}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \DepyFile{p:\pytx@outputdir/#3.stderr:mode=#1}% + }% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent stderr content}}% +} +\def\stderrpythontex{% + \Depythontex{cmd:stderrpythontex:oo:p}% + \@ifnextchar[{\pytx@Stderr}{\pytx@Stderr[verbatim]}% +} +\def\pytx@Stderr[#1]{% + \@ifnextchar[{\pytx@Stderr@i[#1]}{\pytx@Stderr@i[#1][]}% +} +\def\pytx@Stderr@i[#1][#2]{% + \pytx@FetchStderrfile[#1][#2]{\pytx@stdfile}% +} +\def\savestderrpythontex#1{% + \Depythontex{cmd:savestderrpythontex:m:n}% + \ifcsname pytx@SVerr@#1\endcsname + \PackageError{\pytx@packagename}% + {Attempt to save content using an already-defined name}% + {Use a name that is not already defined}% + \else + \expandafter\edef\csname pytx@SVerr@#1\endcsname{\pytx@stdfile}% + \fi +} +\def\usestderrpythontex{% + \Depythontex{cmd:usestderrpythontex:oom:p}% + \@ifnextchar[{\pytx@UseStderr}{\pytx@UseStderr[verb]}% +} +\def\pytx@UseStderr[#1]{% + \@ifnextchar[{\pytx@UseStderr@i[#1]}{\pytx@UseStderr@i[#1][]}% +} +\def\pytx@UseStderr@i[#1][#2]#3{% + \ifcsname pytx@SVerr@#3\endcsname + \pytx@FetchStderrfile[#1][#2]{\csname pytx@SVerr@#3\endcsname}% + \else + \textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent saved stderr content}% + \fi +} +\ifbool{pytx@opt@depythontex}{% + \newwrite\pytx@depyfile + \immediate\openout\pytx@depyfile=\jobname.depytx +}{} +\let\pytx@lexer\relax +\def\Depythontex@orig#1{% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX\pytx@delimchar#1:% + \the\inputlineno:\ifx\pytx@lexer\relax\else\pytx@lexer\fi\pytx@delimchar}% + \let\pytx@lexer\relax} +\ifbool{pytx@opt@depythontex}% + {\let\Depythontex\Depythontex@orig}% + {\let\Depythontex\@gobble} +\ifbool{pytx@opt@depythontex}{% +\AtEndDocument{% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar version=% + \pytx@packageversion\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar macrofile=% + \pytx@outputdir/\pytx@jobname.pytxmcr\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar outputdir=% + \pytx@outputdir\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar graphicx=% + \ifcsname graphicspath\endcsname true\else false\fi\pytx@delimchar}% + \immediate\write\pytx@depyfile{=>DEPYTHONTEX:SETTINGS\pytx@delimchar gobble=% + \pytx@opt@gobble\pytx@delimchar}% +}% +}{} +\def\DepyMacro@orig#1{% + \immediate\write\pytx@depyfile{MACRO:#1}% +} +\ifbool{pytx@opt@depythontex}% + {\let\DepyMacro\DepyMacro@orig}% + {\let\DepyMacro\@gobble} + +\def\DepyFile@orig#1{% + \immediate\write\pytx@depyfile{FILE:#1}% +}% +\ifbool{pytx@opt@depythontex}% + {\let\DepyFile\DepyFile@orig}% + {\let\DepyFile\@gobble} +\newcommand{\pytx@DepyListing@write}{% + \immediate\write\pytx@depyfile{LISTING:firstnumber=\arabic{FancyVerbLine}}% +} +\def\DepyListing@orig{% + \let\oldFancyVerbLine\theFancyVerbLine + \def\theFancyVerbLine{% + \pytx@DepyListing@write + \expandafter\gdef\expandafter\theFancyVerbLine\expandafter{\oldFancyVerbLine}% + \theFancyVerbLine + }% +} +\ifbool{pytx@opt@depythontex}% + {\let\DepyListing\DepyListing@orig}% + {\let\DepyListing\@empty} +\newcommand{\DepythontexOn}{% + \let\Depythontex\Depythontex@orig + \let\DepyMacro\DepyMacro@orig + \let\DepyFile\DepyFile@orig + \let\DepyListing\DepyListing@orig +} +\newcommand{\DepythontexOff}{% + \let\Depythontex\@gobble + \let\DepyMacro\@gobble + \let\DepyFile\@gobble + \let\DepyListing\@empty +} +\def\pytx@Inline{% + \begingroup + \let\do\@makeother\dospecials + \catcode`\{=1 + \@ifnextchar[{\endgroup\pytx@InlineOarg}{\endgroup\pytx@InlineOarg[]}% +}% +\def\pytx@InlineOarg[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \begingroup + \let\do\@makeother\dospecials + \catcode`\{=1 + \catcode`\}=2 + \catcode`\ =\active + \catcode`\^^I=\active + \@ifnextchar\bgroup + {\pytx@InlineMargBgroup}% + {\catcode`\{=12 + \catcode`\}=12 + \pytx@InlineMargOther}% +} +\def\pytx@InlineMargOther#1{% + \endgroup + \ifstrequal{#1}{ }{% + \PackageError{\pytx@packagename}% + {The space character cannot be used as a delimiting character}% + {Choose another delimiting character}}{}% + \def\pytx@InlineMargOtherGet##1#1{\pytx@InlineMargBgroup{##1}}% + \begingroup + \let\do\@makeother\dospecials + \pytx@InlineMargOtherGet +} +\def\pytx@InlineMargBgroup#1{% + \endgroup + \def\pytx@argdetok{\detokenize{#1}}% + \def\pytx@arg{#1}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \pytx@InlineShow + \pytx@InlineSave + \pytx@InlinePrint + \stepcounter{\pytx@counter}% +}% +\def\pytx@InlineShowFV{% + \def\pytx@argretok{% + \begingroup + \everyeof{\noexpand}% + \endlinechar-1\relax + \let\do\@makeother\dospecials + \catcode`\ =\active + \catcode`\^^I=\active + \expandafter\scantokens\expandafter{\pytx@arg}% + \endgroup}% + \begingroup + \pytx@FVSet + \FV@BeginVBox + \frenchspacing + \FV@SetupFont + \FV@DefineWhiteSpace + \FancyVerbDefineActive + \FancyVerbFormatCom + \FV@ObeyTabsInit + \hbox{\FancyVerbFormatLine{\pytx@argretok}}% + \FV@EndVBox + \endgroup +} +\def\pytx@InlineShowPyg{% + \begingroup + \pytx@FVSet + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \BUseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \else + \textbf{??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}% + \fi + \endgroup +} +\def\pytx@InlineSaveCode{% + \pytx@WriteCodefileInfo + \immediate\write\pytx@codefile{\pytx@argdetok}% +} +\let\pytx@InlineAutoprint\@empty +\AtBeginDocument{ + \def\pytx@InlineAutoprint{% + \ifbool{pytx@opt@autoprint}{% + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.stdout}% + {\DepyFile{p:\pytx@outputdir/\pytx@stdfile.stdout}}{}}{}% + } + \ifbool{pytx@opt@stdout}{}{\let\pytx@InlineAutoprint\@empty} +} +\def\pytx@InlineMacroprint{% + \edef\pytx@mcr{pytx@MCR@\pytx@type @\pytx@session @\pytx@group @\arabic{\pytx@counter}}% + \ifcsname\pytx@mcr\endcsname + \csname\pytx@mcr\endcsname + \DepyMacro{p:\pytx@mcr}% + \else + \textbf{??}% + \PackageWarning{\pytx@packagename}{Missing autoprint content}% + \fi +} +\ifbool{pytx@opt@stdout}{}{\let\pytx@InlineMacroprint\@empty} +\def\pytx@InlineMacroprintFV{% + \edef\pytx@mcr{pytx@\pytx@type @\pytx@session @\pytx@group @\arabic{\pytx@counter}}% + \ifcsname FV@SV@\pytx@mcr\endcsname + \BUseVerbatim{\pytx@mcr}% + \DepyMacro{c:\pytx@mcr}% + \else + \textbf{??}% + \PackageWarning{\pytx@packagename}{Missing autoprint content}% + \fi +} +\ifbool{pytx@opt@stdout}{}{\let\pytx@InlineMacroprint\@empty} +\newcommand{\pytx@MakeInlinebFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1b\endcsname}{% + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{cmd:#1b:ov:c}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{b}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\pytx@InlineShowFV + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +\newcommand{\pytx@MakeInlinebPyg}[1]{% + \expandafter\newcommand\expandafter{\csname #1b\endcsname}{% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{b}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\pytx@InlineShowPyg + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +\newcommand{\pytx@MakeInlinevFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1v\endcsname}{% + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{cmd:#1v:ov:c}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowFV + \let\pytx@InlineSave\@empty + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +\newcommand{\pytx@MakeInlinevPyg}[1]{% + \expandafter\newcommand\expandafter{\csname #1v\endcsname}{% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowPyg + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline + }% +}% +\newcommand{\pytx@MakeInlinecFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1c\endcsname}{% + \Depythontex{cmd:#1c:ov:p}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{c}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\pytx@InlineAutoprint + \pytx@Inline + }% +}% +\let\pytx@MakeInlinecPyg\pytx@MakeInlinecFV +\newcommand{\pytx@MakeInlineFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1\endcsname}{% + \Depythontex{cmd:#1:ov:p}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{i}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\pytx@InlineMacroprint + \pytx@Inline + }% +}% +\let\pytx@MakeInlinePyg\pytx@MakeInlineFV +\newcommand{\pytx@MakeInlineConsFV}[1]{% + \expandafter\newcommand\expandafter{\csname #1\endcsname}{% + \Depythontex{cmd:#1:ov:c}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{i}% + \pytx@SetContext + \pytx@SetGroup + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\pytx@InlineMacroprintFV + \pytx@Inline + }% +}% +\let\pytx@MakeInlineConsPyg\pytx@MakeInlineConsFV +\newcommand{\pythontexcustomc}[2][begin]{% + \Depythontex{cmd:pythontexcustomc:omv:p}% + \ifstrequal{#1}{begin}{}{% + \ifstrequal{#1}{end}{}{\PackageError{\pytx@packagename}% + {Invalid optional argument for \string\pythontexcustomc}{} + }% + }% + \xdef\pytx@type{CC:#2:#1}% + \edef\pytx@cmd{c}% + \def\pytx@context{}% + \def\pytx@group{none}% + \let\pytx@InlineShow\@empty + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@Inline[none]% +}% +\def\setpythontexcustomcode#1{% + \Depythontex{cmd:setpythontexcustomcode:mv:p}% + \PackageWarning{\pytx@packagename}{The command + \string\setpythontexcustomcode\space has been deprecated; + use \string\pythontexcustomc\space or pythontexcustomcode instead}% + \begingroup + \let\do\@makeother\dospecials + \catcode`\{=1 + \catcode`\}=2 + \catcode`\^^M=10\relax + \pytx@SetCustomCode{#1}% +} +\long\def\pytx@SetCustomCode#1#2{% + \endgroup + \pythontexcustomc{#1}{pythontexcustomcode=[#2]; + exec('for expr in pythontexcustomcode: exec(expr)'); + del(pythontexcustomcode)} +} +\@onlypreamble\setpythontexcustomcode +\begingroup +\catcode`\^^M=\active +\gdef\pytx@FancyVerbGetLine#1^^M{% + \@nil% + \FV@CheckEnd{#1}% + \ifx\@tempa\FV@EnvironName% + \ifx\@tempb\FV@@@CheckEnd\else\FV@BadEndError\fi% + \let\next\FV@EndScanning% + \else% + \def\FV@Line{#1}% + \def\next{\FV@PreProcessLine\FV@GetLine}% + \immediate\write\pytx@codefile{\detokenize{#1}}% + \fi% + \next}% +\endgroup +\newcommand{\pytx@MakeBlockFV}[1]{% + \expandafter\newenvironment{#1block}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1block:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{block}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginBlockEnvFV}{\endgroup\pytx@BeginBlockEnvFV[]}% + }% + {\end{Verbatim}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\def\pytx@BeginBlockEnvFV[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@WriteCodefileInfo + \let\FancyVerbGetLine\pytx@FancyVerbGetLine + \pytx@FVSet + \begin{Verbatim}% +} +\newcommand{\pytx@MakeVerbFV}[1]{% + \expandafter\newenvironment{#1verbatim}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1verbatim:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{verbatim}% + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginVerbEnvFV}{\endgroup\pytx@BeginVerbEnvFV[]}% + }% + {\end{Verbatim}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\def\pytx@BeginVerbEnvFV[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \begin{Verbatim}% +} +\newcommand{\pytx@MakePygEnv}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \xdef\pytx@type{#1}% + \edef\pytx@cmd{#2}% + \pytx@SetContext + \ifstrequal{#2}{block}{\pytx@SetGroup}{} + \ifstrequal{#2}{verbatim}{\pytx@SetGroupVerb}{} + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginEnvPyg}{\endgroup\pytx@BeginEnvPyg[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +}% +\def\pytx@BeginEnvPyg[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@WriteCodefileInfo + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginEnvPyg@i}{\endgroup\pytx@BeginEnvPyg@i[]}% +}% +\def\pytx@BeginEnvPyg@i[#1]{% + \def\pytx@fvopttmp{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +}% +\newcommand{\pytx@MakeBlockPyg}[1]{\pytx@MakePygEnv{#1}{block}} +\newcommand{\pytx@MakeVerbPyg}[1]{\pytx@MakePygEnv{#1}{verbatim}} +\def\pytx@WriteDetok#1{% + \immediate\write\pytx@codefile{\detokenize{#1}}}% +\def\pytx@FVB@VerbatimOut{% + \@bsphack + \begingroup + \let\FV@ProcessLine\pytx@WriteDetok + \let\FV@FontScanPrep\relax + \let\@noligs\relax + \FV@Scan}% +\def\pytx@FVE@VerbatimOut{\endgroup\@esphack}% +\let\pytx@EnvAutoprint\@empty +\AtBeginDocument{ + \def\pytx@EnvAutoprint{% + \ifbool{pytx@opt@autoprint}{% + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.stdout}% + {\DepyFile{p:\pytx@outputdir/\pytx@stdfile.stdout}}{}}{}% + } + \ifbool{pytx@opt@stdout}{}{\let\pytx@EnvAutoprint\@empty} +} +\newcommand{\pytx@MakeCodeFV}[1]{% + \expandafter\newenvironment{#1code}{% + \VerbatimEnvironment + \Depythontex{env:#1code:oo|:p}% + \xdef\pytx@type{#1}% + \edef\pytx@cmd{code}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginCodeEnv}{\endgroup\pytx@BeginCodeEnv[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \pytx@EnvAutoprint + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +}% +\def\pytx@BeginCodeEnv[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \pytx@WriteCodefileInfo + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginCodeEnv@i}{\endgroup\pytx@BeginCodeEnv@i[]}% +}% +\def\pytx@BeginCodeEnv@i[#1]{% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +}% +\let\pytx@MakeCodePyg\pytx@MakeCodeFV +\newenvironment{pythontexcustomcode}[2][begin]{% + \VerbatimEnvironment + \Depythontex{env:pythontexcustomcode:om:n}% + \ifstrequal{#1}{begin}{}{% + \ifstrequal{#1}{end}{}{\PackageError{\pytx@packagename}% + {Invalid optional argument for pythontexcustomcode}{} + }% + }% + \xdef\pytx@type{CC:#2:#1}% + \edef\pytx@cmd{code}% + \def\pytx@context{}% + \def\pytx@group{none}% + \pytx@BeginCodeEnv[none]}% +{\end{VerbatimOut}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% +}% +\newcommand{\pytx@MakeConsFV}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1#2:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{console}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginConsEnvFV}{\endgroup\pytx@BeginConsEnvFV[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \DepyMacro{c:\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.tex}% + {\DepyFile{c:\pytx@outputdir/\pytx@stdfile.tex}}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent console content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\def\pytx@BeginConsEnvFV[#1]{% + \ifstrempty{#1}{\edef\pytx@session{default}}{\StrSubstitute{#1}{:}{-}[\pytx@session]}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@WriteCodefileInfo + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginConsEnvFV@i}{\endgroup\pytx@BeginConsEnvFV@i[]}% +}% +\def\pytx@BeginConsEnvFV@i[#1]{% + \def\pytx@fvopttmp{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +}% +\newcommand{\pytx@MakeConsPyg}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1#2:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{console}% + \pytx@SetContext + \pytx@SetGroup + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginConsEnvFV}{\endgroup\pytx@BeginConsEnvFV[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \DepyMacro{c:\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.pygtex}% + {\DepyFile{c:\pytx@outputdir/\pytx@stdfile.pygtex}}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent console content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\pgfkeys{ + /PYTX/family/.is family, + /PYTX/family, + prettyprinter/.estore in = \pytx@tmp@pprinter, + pyglexer/.estore in = \pytx@tmp@pyglexer, + pygopt/.estore in = \pytx@tmp@pygopt, + console/.estore in = \pytx@tmp@console, + default/.style = {prettyprinter=auto, pyglexer=text, pygopt={}, console=false} +} +\def\pytx@MakeFamilyFV#1{% + \pytx@MakeInlinebFV{#1}% + \pytx@MakeInlinevFV{#1}% + \pytx@MakeInlineFV{#1}% + \pytx@MakeBlockFV{#1}% + \pytx@MakeVerbFV{#1}% +} +\def\pytx@MakeFamilyPyg#1{% + \ifbool{pytx@opt@pyginline}% + {\pytx@MakeInlinebPyg{#1}\pytx@MakeInlinevPyg{#1}}% + {\pytx@MakeInlinebFV{#1}\pytx@MakeInlinevFV{#1}}% + \pytx@MakeInlinePyg{#1}% + \pytx@MakeBlockPyg{#1}% + \pytx@MakeVerbPyg{#1}% + \booltrue{pytx@usedpygments}% + \AtEndDocument{\immediate\write\pytx@codefile{pygfamily=#1|% + \csname pytx@pyglexer@#1\endcsname|% + \csname pytx@pygopt@#1\endcsname}% + }% +} +\def\pytx@MakeFamilyFVCons#1{% + \pytx@MakeInlinevFV{#1}% + \pytx@MakeInlineConsFV{#1}% + \pytx@MakeConsFV{#1}{sole}% + \pytx@MakeVerbFV{#1}% +} +\def\pytx@MakeFamilyPygCons#1{% + \ifbool{pytx@opt@pyginline}% + {\pytx@MakeInlinevPyg{#1}}% + {\pytx@MakeInlinevFV{#1}}% + \pytx@MakeInlineConsPyg{#1}% + \pytx@MakeConsPyg{#1}{sole}% + \pytx@MakeVerbPyg{#1}% + \booltrue{pytx@usedpygments}% + \AtEndDocument{\immediate\write\pytx@codefile{pygfamily=#1|% + \csname pytx@pyglexer@#1\endcsname|% + \csname pytx@pygopt@#1\endcsname}% + }% +} +\newcommand{\makepythontexfamily}[2][]{% + \IfBeginWith{#2}{PYG}% + {\PackageError{\pytx@packagename}% + {Attempt to create macros with reserved prefix PYG}{}}{}% + \pgfkeys{/PYTX/family, default, #1} + \expandafter\xdef\csname pytx@macroformatter@#2\endcsname{\pytx@tmp@pprinter} + \expandafter\gdef\csname pytx@fvsettings@#2\endcsname{} + \expandafter\xdef\csname pytx@pyglexer@#2\endcsname{\pytx@tmp@pyglexer} + \expandafter\xdef\csname pytx@pygopt@#2\endcsname{\pytx@tmp@pygopt} + \expandafter\xdef\csname pytx@console@#2\endcsname{\pytx@tmp@console} + \pytx@MakeInlinecFV{#2} + \pytx@MakeCodeFV{#2} + \AtBeginDocument{% + \ifcsstring{pytx@macroformatter@#2}{auto}{% + \ifbool{pytx@opt@pygments}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyPygCons{#2}}{\pytx@MakeFamilyPyg{#2}}}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyFVCons{#2}}{\pytx@MakeFamilyFV{#2}}}% + }{}% + \ifcsstring{pytx@macroformatter@#2}{fancyvrb}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyFVCons{#2}}{\pytx@MakeFamilyFV{#2}}}{}% + \ifcsstring{pytx@macroformatter@#2}{pygments}% + {\ifcsstring{pytx@console@#2}{true}% + {\pytx@MakeFamilyPygCons{#2}}{\pytx@MakeFamilyPyg{#2}}}{}% + }% +} +\@onlypreamble\makepythontexfamily +\newcommand{\setpythontexpyglexer}[2][]{% + \Depythontex{cmd:setpythontexpyglexer:om:n}% + \ifstrempty{#1}{\def\pytx@pyglexer{#2}}{% + \ifcsname pytx@pyglexer@#1\endcsname + \expandafter\xdef\csname pytx@pyglexer@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify a non-existent family}{}% + \fi + }% +}% +\@onlypreamble\setpythontexpyglexer +\newcommand{\setpythontexpygopt}[2][]{% + \Depythontex{cmd:setpythontexpygopt:om:n}% + \ifstrempty{#1}{\def\pytx@pygopt{#2}}{% + \ifcsname pytx@pygopt@#1\endcsname + \expandafter\xdef\csname pytx@pygopt@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify Pygments options for a non-existent family}{}% + \fi + }% +} +\@onlypreamble\setpythontexpygopt +\newcommand{\setpythontexprettyprinter}[2][]{% + \Depythontex{cmd:setpythontexprettyprinter:om:n}% + \ifstrempty{#1}{% + \ifstrequal{#2}{fancyvrb}{\boolfalse{pytx@opt@pygments}}% + \ifstrequal{#2}{pygments}{\booltrue{pytx@opt@pygments}}% + }{% + \ifcsname pytx@macroformatter@#1\endcsname + \ifbool{pytx@opt@depythontex}{}{% + \expandafter\xdef\csname pytx@macroformatter@#1\endcsname{#2}} + \else + \PackageError{\pytx@packagename}% + {Cannot modify a family that does not exist or does not allow formatter choices}% + {Create the family with \string\makepythontexfamily}% + \fi + }% +} +\@onlypreamble\setpythontexprettyprinter +\makepythontexfamily[pyglexer=python]{py} +\makepythontexfamily[pyglexer=pycon, console=true]{pycon} +\makepythontexfamily[pyglexer=python]{sympy} +\makepythontexfamily[pyglexer=pycon, console=true]{sympycon} +\makepythontexfamily[pyglexer=python]{pylab} +\makepythontexfamily[pyglexer=pycon, console=true]{pylabcon} +\renewcommand{\do}[1]{% + \ifstrequal{#1}{ruby}{\makepythontexfamily[pyglexer=ruby]{ruby}}{}% + \ifstrequal{#1}{rb}{\makepythontexfamily[pyglexer=ruby]{rb}}{}% + \ifstrequal{#1}{julia}{\makepythontexfamily[pyglexer=julia]{julia}}{}% + \ifstrequal{#1}{jl}{\makepythontexfamily[pyglexer=julia]{jl}}{}% +} +\expandafter\docsvlist\expandafter{\pytx@families} +\newbool{pytx@listingenv} +\def\setpythontexlistingenv#1{% + \Depythontex{cmd:setpythontexlistingenv:m:n}% + \DeclareFloatingEnvironment[fileext=lopytx,listname={List of Listings},name=Listing]{#1} + \booltrue{pytx@listingenv} +} +\@onlypreamble\setpythontexlistingenv +\AtBeginDocument{ + \ifcsname listing\endcsname + \ifbool{pytx@listingenv}{}% + {\PackageWarning{\pytx@packagename}% + {A "listing" environment already exists \MessageBreak + \pytx@packagename\space will not create one \MessageBreak + Use \string\setpythontexlistingenv to create a custom listing environment}}% + \else + \ifbool{pytx@listingenv}{}{\DeclareFloatingEnvironment[fileext=lopytx]{listing}} + \fi +} +\def\pytx@CheckPygmentsInit#1{% + \ifcsname pytx@fvsettings@PYG#1\endcsname\else + \expandafter\gdef\csname pytx@fvsettings@PYG#1\endcsname{}% + \expandafter\gdef\csname pytx@pygopt@PYG#1\endcsname{}% + \AtEndDocument{\immediate\write\pytx@codefile{pygfamily=PYG#1|#1|% + \csname pytx@pygopt@PYG#1\endcsname}}% + \fi +} +\def\pytx@MakePygmentsInlineFV{% + \newcommand{\pygment}[1]{% + \edef\pytx@lexer{##1}% + \Depythontex{cmd:pygment:mv:c}% + \edef\pytx@type{PYG##1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowFV + \let\pytx@InlineSave\@empty + \let\pytx@InlinePrint\@empty + \pytx@CheckPygmentsInit{##1}% + \pytx@Inline[]% + }% +} +\def\pytx@MakePygmentsInlinePyg{% + \newcommand{\pygment}[1]{% + \edef\pytx@type{PYG##1}% + \edef\pytx@cmd{v}% + \pytx@SetContext + \pytx@SetGroupVerb + \let\pytx@InlineShow\pytx@InlineShowPyg + \let\pytx@InlineSave\pytx@InlineSaveCode + \let\pytx@InlinePrint\@empty + \pytx@CheckPygmentsInit{##1}% + \pytx@Inline[] + }% +} +\def\pytx@MakePygmentsEnvFV{% + \newenvironment{pygments}{% + \VerbatimEnvironment + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BEPygmentsFV}{\endgroup\pytx@BEPygmentsFV[]}% + }% + {\end{Verbatim}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + }% +} +\def\pytx@BEPygmentsFV[#1]{% + \def\pytx@fvopttmp{#1}% + \begingroup + \obeylines + \pytx@BEPygmentsFV@i +} +\def\pytx@BEPygmentsFV@i#1{% + \endgroup + \edef\pytx@type{PYG#1}% + \edef\pytx@lexer{#1}% + \Depythontex{env:pygments:om:c}% + \DepyListing + \edef\pytx@cmd{verbatim}% + \edef\pytx@session{default}% + \edef\pytx@linecount{pytx@\pytx@type @\pytx@session @\pytx@group @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@CheckPygmentsInit{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \begin{Verbatim}% +} +\def\pytx@MakePygmentsEnvPyg{% + \newenvironment{pygments}{% + \VerbatimEnvironment + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BEPygmentsPyg}{\endgroup\pytx@BEPygmentsPyg[]}% + }% + {\end{VerbatimOut}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/% + \pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\def\pytx@BEPygmentsPyg[#1]{% + \def\pytx@fvopttmp{#1}% + \begingroup + \obeylines + \pytx@BEPygmentsPyg@i +} +\def\pytx@BEPygmentsPyg@i#1{% + \endgroup + \edef\pytx@type{PYG#1}% + \edef\pytx@cmd{verbatim}% + \edef\pytx@session{default}% + \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}% + \pytx@CheckCounter{\pytx@counter}% + \edef\pytx@linecount{\pytx@counter @line}% + \pytx@CheckCounter{\pytx@linecount}% + \pytx@WriteCodefileInfo + \pytx@CheckPygmentsInit{#1}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \let\FVB@VerbatimOut\pytx@FVB@VerbatimOut + \let\FVE@VerbatimOut\pytx@FVE@VerbatimOut + \begin{VerbatimOut}% +} +\def\pytx@MakePygmentsInputFV{ + \newcommand{\inputpygments}[3][]{% + \edef\pytx@lexer{##2}% + \Depythontex{cmd:inputpygments:omm:c}% + \edef\pytx@type{PYG##2}% + \edef\pytx@cmd{verbatim}% + \pytx@SetContext + \pytx@SetGroupVerb + \edef\pytx@session{EXT:##3}% + \pytx@CheckPygmentsInit{##2}% + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \begingroup + \DepyListing %Always must be in a group + \pytx@FVSet + \fvset{firstnumber=auto}% + \IfFileExists{##3}% + {\DepyFile{c:##3:mode=verbatim}\VerbatimInput[##1]{##3}}% + {\PackageWarning{\pytx@packagename}{Input file <##3> doesn't exist}}% + \endgroup + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + }% +} +\def\pytx@MakePygmentsInputPyg{ + \newcommand{\inputpygments}[3][]{% + \edef\pytx@type{PYG##2}% + \edef\pytx@cmd{verbatim}% + \pytx@SetContext + \pytx@SetGroupVerb + \edef\pytx@session{EXT:##3}% + \pytx@CheckPygmentsInit{##2}% + \pytx@WriteCodefileInfoExt + \IfFileExists{##3}{}{\PackageWarning{\pytx@packagename}% + {Input file <##3> does not exist}} + \setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}% + \begingroup + \pytx@FVSet + \fvset{firstnumber=auto}% + \ifcsname FV@SV@pytx@\pytx@type @\pytx@session @\pytx@group @0\endcsname + \UseVerbatim[##1]{pytx@\pytx@type @\pytx@session @\pytx@group @0}% + \else + \InputIfFileExists{\pytx@outputdir/##3_##2.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \endgroup + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + }% +} +\newcommand{\setpygmentsfv}[2][]{% + \Depythontex{cmd:setpygmentsfv:om:n}% + \ifstrempty{#1}% + {\gdef\pytx@fvsettings{#2}}% + {\expandafter\gdef\csname pytx@fvsettings@PYG#1\endcsname{#2}}% +}% +\newcommand{\setpygmentspygopt}[2][]{% + \Depythontex{cmd:setpygmentspygopt:om:n}% + \ifstrempty{#1}{\def\pytx@pygopt{#2}}{% + \expandafter\gdef\csname pytx@pygopt@PYG#1\endcsname{#2}}% +} +\@onlypreamble\setpygmentspygopt +\newcommand{\setpygmentsprettyprinter}[1]{% + \Depythontex{cmd:setpygmentsprettyprinter:m:n}% + \ifstrequal{#1}{fancyvrb}{\boolfalse{pytx@opt@pygments}}% + \ifstrequal{#1}{pygments}{\booltrue{pytx@opt@pygments}}% +} +\@onlypreamble\setpygmentsprettyprinter +\xdef\pytx@macroformatter@PYG{auto} +\def\makepygmentsfv{% + \pytx@MakePygmentsInlineFV + \pytx@MakePygmentsEnvFV + \pytx@MakePygmentsInputFV +}% +\@onlypreamble\makepygmentsfv +\def\makepygmentspyg{% + \ifbool{pytx@opt@pyginline}% + {\pytx@MakePygmentsInlinePyg}% + {\pytx@MakePygmentsInlineFV}% + \pytx@MakePygmentsEnvPyg + \pytx@MakePygmentsInputPyg + \booltrue{pytx@usedpygments} +}% +\@onlypreamble\makepygmentspyg +\def\makepygments{% + \AtBeginDocument{% + \ifdefstring{\pytx@macroformatter@PYG}{auto}% + {\ifbool{pytx@opt@pygments}% + {\makepygmentspyg}{\makepygmentsfv}}{} + \ifdefstring{\pytx@macroformatter@PYG}{pygments}% + {\makepygmentspyg}{} + \ifdefstring{\pytx@macroformatter@PYG}{fancyvrb}% + {\makepygmentsfv}{} + }% +}% +\@onlypreamble\makepygments +\makepygments +\AfterEndDocument{% + \immediate\closeout\pytx@codefile + \ifbool{pytx@opt@depythontex}{\immediate\closeout\pytx@depyfile}{}% +} +\ifbool{pytx@opt@beta}{ + +\def\pytx@FetchStdoutfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stdout}{% + \ifstrempty{#1}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{verb}{\VerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{inlineverb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \ifstrequal{#1}{v}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stdout}}{}% + \DepyFile{p:\pytx@outputdir/#3.stdout:mode=#1}% + }% + {\pytx@stdout@warntext + \PackageWarning{\pytx@packagename}{Non-existent printed content}}% +} +\def\pytx@FetchStderrfile[#1][#2]#3{% + \IfFileExists{\pytx@outputdir/#3.stderr}{% + \ifstrequal{#1}{raw}{\input{\pytx@outputdir/#3.stderr}}{}% + \ifstrempty{#1}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{verb}{\VerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{inlineverb}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \ifstrequal{#1}{v}{\BVerbatimInput[#2]{\pytx@outputdir/#3.stderr}}{}% + \DepyFile{p:\pytx@outputdir/#3.stderr:mode=#1}% + }% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent stderr content}}% +} + +\renewcommand{\pytx@MakeVerbFV}[1]{% + \expandafter\newenvironment{#1verb}{% + \VerbatimEnvironment + \expandafter\let\expandafter\pytx@lexer\csname pytx@pyglexer@#1\endcsname + \Depythontex{env:#1verb:oo|:c}% + \DepyListing + \xdef\pytx@type{#1}% + \edef\pytx@cmd{verb}% + \pytx@SetContext + \pytx@SetGroupVerb + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginVerbEnvFV}{\endgroup\pytx@BeginVerbEnvFV[]}% + }% + {\end{Verbatim}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +} +\renewcommand{\pytx@MakePygEnv}[2]{% + \expandafter\newenvironment{#1#2}{% + \VerbatimEnvironment + \xdef\pytx@type{#1}% + \edef\pytx@cmd{#2}% + \pytx@SetContext + \ifstrequal{#2}{block}{\pytx@SetGroup}{} + \ifstrequal{#2}{verb}{\pytx@SetGroupVerb}{} + \begingroup + \obeylines + \@ifnextchar[{\endgroup\pytx@BeginEnvPyg}{\endgroup\pytx@BeginEnvPyg[]}% + }% + {\end{VerbatimOut}% + \xdef\pytx@stdfile{\pytx@type_\pytx@session_\pytx@group_\arabic{\pytx@counter}}% + \setcounter{FancyVerbLine}{\value{\pytx@linecount}}% + \pytx@FVSet + \ifdefstring{\pytx@fvopttmp}{}{}{\expandafter\fvset\expandafter{\pytx@fvopttmp}}% + \ifcsname FV@SV@\pytx@counter @\arabic{\pytx@counter}\endcsname + \UseVerbatim{\pytx@counter @\arabic{\pytx@counter}}% + \else + \InputIfFileExists{\pytx@outputdir/\pytx@stdfile.pygtex}{}% + {\textbf{??~\pytx@packagename~??}% + \PackageWarning{\pytx@packagename}{Non-existent Pygments content}}% + \fi + \setcounter{\pytx@linecount}{\value{FancyVerbLine}}% + \setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}% + \stepcounter{\pytx@counter}% + }% +}% +\renewcommand{\pytx@MakeVerbPyg}[1]{\pytx@MakePygEnv{#1}{verb}} + +\def\setpythontexpyglexer#1#2{% + \Depythontex{cmd:setpythontexpyglexer:mm:n}% + \ifcsname pytx@pyglexer@#1\endcsname + \expandafter\xdef\csname pytx@pyglexer@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify a non-existent family}{}% + \fi +}% +\renewcommand{\setpythontexpygopt}[2]{% + \Depythontex{cmd:setpythontexpygopt:mm:n}% + \ifcsname pytx@pygopt@#1\endcsname + \expandafter\xdef\csname pytx@pygopt@#1\endcsname{#2}% + \else + \PackageError{\pytx@packagename}% + {Cannot modify Pygments options for a non-existent family}{}% + \fi +} +\def\setpygmentspygopt#1#2{% + \Depythontex{cmd:setpygmentspygopt:mm:n}% + \AtEndDocument{\immediate\write\pytx@codefile{% + \pytx@delimsettings pygfamily=PYG#1,#1,% + \string{#2\string}\pytx@delimchar}% + }% +} + +\def\setpythontexformatter#1#2{% + \Depythontex{cmd:setpythontexformatter:mm:n}% + \ifcsname pytx@macroformatter@#1\endcsname + \ifbool{pytx@opt@depythontex}{}{% + \expandafter\xdef\csname pytx@macroformatter@#1\endcsname{#2}} + \else + \PackageError{\pytx@packagename}% + {Cannot modify a family that does not exist or does not allow formatter choices}% + {Create the family with \string\makepythontexfamily}% + \fi +} +\@onlypreamble\setpythontexformatter +\def\setpygmentsformatter#1{% + \Depythontex{cmd:setpygmentsformatter:m:n}% + \ifbool{pytx@opt@depythontex}{}{\xdef\pytx@macroformatter@PYG{#1}}} +\@onlypreamble\setpygmentsformatter + +}{} %End beta +%% \Finale +\endinput +%% +%% End of file `pythontex.sty'. diff --git a/Master/tlpkg/bin/tlpkg-ctan-check b/Master/tlpkg/bin/tlpkg-ctan-check index b0eafea4aeb..004b4ae0a6f 100755 --- a/Master/tlpkg/bin/tlpkg-ctan-check +++ b/Master/tlpkg/bin/tlpkg-ctan-check @@ -385,7 +385,7 @@ my @TLP_working = qw( pstricks-examples-en pstricks_calcnotes psu-thesis ptex2pdf ptext ptptex punk punk-latex punknova purifyeps pxbase pxchfon pxcjkcat pxfonts pxgreeks pxjahyper - pxpgfmark pxrubrica pxtxalfa python + pxpgfmark pxrubrica pxtxalfa python pythontex quattrocento qcm qobitree quoting qstest qsymbols qtree quotchap quotmark r_und_s raleway ran_toks randbild randomwalk randtext rccol rcs rcs-multi rcsinfo diff --git a/Master/tlpkg/libexec/ctan2tds b/Master/tlpkg/libexec/ctan2tds index a8c08aab95b..70b092c102e 100755 --- a/Master/tlpkg/libexec/ctan2tds +++ b/Master/tlpkg/libexec/ctan2tds @@ -866,7 +866,6 @@ chomp ($Build = `cd $Master/../Build/source && pwd`); 'pxchfon', "&MAKEflatten", 'pxfonts', "&MAKEflatten", 'pxtxalfa', "&MAKEcopy", - 'pythontex', "die 'skipping, python dependencies'", 'pzccal', "die 'skipping, obsoleted by urwchancal'", 'r2bib', "die 'skipping, requires compilation'", 'ragged2e', "die 'skipping, part of ms'", @@ -2374,6 +2373,7 @@ $standardxmt='\.xmt'; 'lualatex-math' => '\.lua$', 'luasseq' => '\.lua$', 'mycv' => 'mycv_split_contents\.pl', + 'pythontex' => '([23]|_.*)\.py$', 'spelling' => '\.lua$', ); @@ -2431,6 +2431,7 @@ $standardxmt='\.xmt'; 'pst2pdf' => 'pst2pdf\.pl$', 'ptex2pdf' => 'ptex2pdf\.lua$', 'purifyeps' => 'purifyeps$', + 'pythontex' => '(de)?pythontex\.py$', 'splitindex' => 'splitindex\.pl$', 'sty2dtx' => '\.pl$', 'svn-multi' => 'svn-multi\.pl$', diff --git a/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc b/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc index 1d15764a6b4..3bf5f8cfc20 100644 --- a/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc +++ b/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc @@ -53,6 +53,7 @@ depend pfarrei depend pkfix depend pkfix-helper depend purifyeps +depend pythontex depend seetexk depend sty2dtx depend synctex diff --git a/Master/tlpkg/tlpsrc/pythontex.tlpsrc b/Master/tlpkg/tlpsrc/pythontex.tlpsrc new file mode 100644 index 00000000000..949681dba72 --- /dev/null +++ b/Master/tlpkg/tlpsrc/pythontex.tlpsrc @@ -0,0 +1 @@ +binpattern f bin/${ARCH}/*${PKGNAME} |