summaryrefslogtreecommitdiff
path: root/Master/texmf-dist
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist')
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/README9
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/pythontex.pdfbin887905 -> 915100 bytes
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdfbin382386 -> 382442 bytes
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex8
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdfbin79542 -> 179367 bytes
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex114
-rw-r--r--Master/texmf-dist/doc/latex/pythontex/syncpdb.py1429
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/depythontex.py11
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/depythontex2.py75
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/depythontex3.py75
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex.py12
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex2.py700
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex3.py700
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_2to3.py4
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_engines.py534
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_install.py494
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_install_texlive.py343
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_utils.py92
-rwxr-xr-xMaster/texmf-dist/source/latex/pythontex/depythontex.bat2
-rwxr-xr-xMaster/texmf-dist/source/latex/pythontex/pythontex.bat4
-rw-r--r--Master/texmf-dist/source/latex/pythontex/pythontex.dtx431
-rwxr-xr-xMaster/texmf-dist/source/latex/pythontex/pythontex_install.bat4
-rw-r--r--Master/texmf-dist/tex/latex/pythontex/pythontex.sty63
23 files changed, 4022 insertions, 1082 deletions
diff --git a/Master/texmf-dist/doc/latex/pythontex/README b/Master/texmf-dist/doc/latex/pythontex/README
index 3c7247e9dcf..a25421f3908 100644
--- a/Master/texmf-dist/doc/latex/pythontex/README
+++ b/Master/texmf-dist/doc/latex/pythontex/README
@@ -3,7 +3,7 @@
===================================================================
:Author: Geoffrey Poore
-:Version: 0.12 (2013/08/26)
+:Version: 0.13 (2014/07/14)
: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
@@ -11,7 +11,7 @@
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.
+It also supports the Ruby, Julia, and Octave 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
@@ -37,8 +37,8 @@ Compiling a PythonTeX document involves three steps:
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,
+instructions. An installation script is provided for TeX Live and MiKTeX.
+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.
@@ -57,6 +57,7 @@ created for pythontex.py and depythontex.py.
- pythontex_engines.py
- pythontex_utils.py
- depythontex.py, depythontex2.py, and depythontex3.py
+ - syncpdb.py
* /source/latex/pythontex/
diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf b/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf
index 4d3c8661c6b..23d50da9790 100644
--- a/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf
+++ b/Master/texmf-dist/doc/latex/pythontex/pythontex.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf
index 66ba25e6e4f..a5fa37a36bd 100644
--- a/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf
+++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex
index 29feb22c309..b74a3672547 100644
--- a/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex
+++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_gallery.tex
@@ -4,19 +4,19 @@
% 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
+\ifx\pdfmatch\undefined
\else
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\fi
% xetex:
-\expandafter\ifx\csname XeTeXinterchartoks\endcsname\relax
+\ifx\XeTeXinterchartoks\undefined
\else
\usepackage{fontspec}
\defaultfontfeatures{Ligatures=TeX}
\fi
% luatex:
-\expandafter\ifx\csname directlua\endcsname\relax
+\ifx\directlua\undefined
\else
\usepackage{fontspec}
\fi
@@ -152,6 +152,8 @@ h = Integral(exp(-phi**2), (phi, 0, oo))
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!
+It is possible to pass page dimensions and similar contextual information from the \LaTeX\ side to the Python side. If you want your figures to be, for example, a particular fraction of the page width, you can pass the value of \pygment{latex}{\textwidth} to the Python side, and use it in creating your figures. See \pygment{latex}{\setpythontexcontext} in the main documentation for details.
+
You may want to use matplotlib's PGF backend when creating plots.
\begin{pylabblock}
diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf
index cacc158dce4..6d5d82d7fd1 100644
--- a/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf
+++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex
index 41d4f43b33b..01bd938aca1 100644
--- a/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex
+++ b/Master/texmf-dist/doc/latex/pythontex/pythontex_quickstart.tex
@@ -1,5 +1,28 @@
\documentclass[twocolumn]{article}
+% Engine-specific settings
+% Detect pdftex/xetex/luatex, and load appropriate font packages.
+% This is inspired by the approach in the iftex package.
+% pdftex:
+\ifx\pdfmatch\undefined
+\else
+ \usepackage[T1]{fontenc}
+ \usepackage[utf8]{inputenc}
+\fi
+% xetex:
+\ifx\XeTeXinterchartoks\undefined
+\else
+ \usepackage{fontspec}
+ \defaultfontfeatures{Ligatures=TeX}
+\fi
+% luatex:
+\ifx\directlua\undefined
+\else
+ \usepackage{fontspec}
+\fi
+% End engine-specific settings
+\usepackage{lmodern}
+
\usepackage[margin=1in]{geometry}
\usepackage{microtype}
@@ -7,8 +30,6 @@
\usepackage[svgnames]{xcolor}
-\usepackage{nopageno}
-
\usepackage{hyperref}
\hypersetup{colorlinks=true,urlcolor=Green,linkcolor=blue}
@@ -28,23 +49,38 @@
+\section*{Installing}
+
+\pytex\ requires Python 2.7 or 3.2+.
+
+\pytex\ is included in TeX Live 2013. It may be installed via the package manager.
+
+A Python installation script is included with the package. It should be able to install the package in most situations. Depending on the configuration of your system, you may have to run the installation script with administrative priviliges.
+
+Detailed installation information is available in the main documentation, \texttt{pythontex.pdf}.
+
\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.
+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, so you can use \texttt{latex}, \texttt{pdflatex}, \texttt{xelatex}, or \texttt{lualatex}. There are minor engine-specific differences; see the main documentation for details.
+
+The last two compile steps are \emph{only} necessary when code needs to be executed or highlighted. Otherwise, the document may be compiled just like a normal \LaTeX\ document; all output is cached.
+\pytex\ is compatible with \texttt{latexmk}. Details for configuring \texttt{latexmk} are provided in the main documentation.
-\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}).
+
+\section*{Basic 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}{\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} or \pygment{latex}{\stdoutpythontex}.
-\pygment{latex}{\pyv} only typesets code. For example, \pygment{latex}{\pyv{var = 2}} produces \pyv{var = 2}.
+\pygment{latex}{\pyv} only typesets code; nothing is executed. For example, \pygment{latex}{\pyv{var = 2}} produces \pyv{var = 2}.
-\section*{Environments}
+\section*{Basic 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}
@@ -61,6 +97,21 @@ print(r'\textit{A message from Python!}')
print(r'\end{center}')
\end{pycode}
+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 main documentation for more details.
+
+
+\section*{More commands/environments}
+
+All commands and environments described so far have names beginning with \texttt{py}. There are equivalent commands and environments that begin with \texttt{sympy}; these automatically include
+\begin{pygments}{python}
+from sympy import *
+\end{pygments}
+There are also equivalent commands and environments that begin with \texttt{pylab}; these automatically use matplotlib's \texttt{pylab} module via
+\begin{pygments}{python}
+from pylab import *
+\end{pygments}
+The \texttt{sympy} and \texttt{pylab} commands and environments execute code in separate sessions from the \texttt{py} commands and environments. This can make it easier to avoid namespace conflicts.
+
There is also a \texttt{pyconsole} environment that emulates a Python interactive console. For example,
\begin{pygments}{text}
\begin{pyconsole}
@@ -73,21 +124,62 @@ yields
var = 1 + 1
var
\end{pyconsole}
+Console variable values may be accessed inline via the \pygment{latex}{\pycon} command. More console information is available in the main documentation.
+
+\section*{Working with Python 2}
+\pytex\ supports both Python 2 and 3. Under Python 2, imports from \pygment{python}{__future__} will work so long as they are the first user-entered code in a given session. \pytex\ imports most things from \pygment{python}{__future__} by default. To control what is automatically imported, see the \texttt{pyfuture} and \texttt{pyconfuture} package options in the main documentation.
+
+
+\section*{Support for additional languages}
+\pytex\ also provides support for additional languages. Currently, Ruby, Julia, and Octave support is included. To enable commands and environments for these language, see the \texttt{usefamily} package option in the main documentation.
-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.
+Language support is provided via a template system; in most cases, a new language can be added with about 100 lines of template code. If you would like support for a new language, please open an issue at \href{https://github.com/gpoore/pythontex}{GitHub}. The main documentation also contains a summary of the process for adding languages.
\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.
+\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. These limitations can be removed by passing arguments verbatim or through catcode trickery. \pytex\ environments cannot normally 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\ provides many additional features. The working and output directories can be specified via \pygment{latex}{\setpythontexworkingdir} and \pygment{latex}{\setpythontexoutputdir}. The user can determine when code is executed with the package option \texttt{rerun}, selecting factors such as modification and exit status. By default, all commands and environments with the same base name (\texttt{py}, \texttt{sympy}, \texttt{pylab}, etc.) 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. The utilities class also allows information such as page width to be passed from the \TeX\ side to Python/other languages. See the main documentation for additional information.
\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.
+Code may be run in interactive mode via the \verb|--interactive| and \verb|--debug| command-line options. This is primarily useful for working with interactive debuggers.
+
+
+\section*{Unicode support}
+
+\pytex\ supports Unicode under all \LaTeX\ engines. For example, consider the following example from Python:
+\begin{pygments}{python}
+my_string = '¥ § ß Ğ Ð Ñ Ö þ ø'
+\end{pygments}
+This requires some engine-specific packages. Typical packages are listed below.
+
+\begin{itemize}
+\item pdfLaTeX:
+\begin{pygments}{latex}
+\usepackage[T1]{fontenc}
+\usepackage[utf8]{inputenc}
+\end{pygments}
+\item LuaLaTeX:
+\begin{pygments}{latex}
+\usepackage{fontspec}
+\end{pygments}
+\item XeLaTeX:
+\begin{pygments}{latex}
+\usepackage{fontspec}
+\defaultfontfeatures{Ligatures=TeX}
+\end{pygments}
+\end{itemize}
+
+If you are using Python 2, you will also need to specify that you are using Unicode. You may want
+\begin{pygments}{python}
+from __future__ import unicode_literals
+\end{pygments}
+at the beginning of your Python code. Or you can just load the \pytex\ package with the option \verb|pyfuture=all|, which will import \verb|unicode_literals| automatically.
\end{document}
diff --git a/Master/texmf-dist/doc/latex/pythontex/syncpdb.py b/Master/texmf-dist/doc/latex/pythontex/syncpdb.py
new file mode 100644
index 00000000000..fa4ee6e8b80
--- /dev/null
+++ b/Master/texmf-dist/doc/latex/pythontex/syncpdb.py
@@ -0,0 +1,1429 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+'''
+Synchronized Python Debugger (syncpdb)
+
+Provides a wrapper for pdb that synchronizes code line numbers with the line
+numbers of a document from which the code was extracted. This allows pdb to
+be used more effectively with literate programming-type systems. The wrapper
+was initially created to work with PythonTeX, which allows Python code
+entered within a LaTeX document to be executed. In that case, syncpdb makes
+possible debugging in which both the code line numbers, and the corresponding
+line numbers in the LaTeX document, are displayed.
+
+All pdb commands function normally. In addition, commands that take a line
+number or filename:lineno as an argument will also take these same values
+with a percent symbol (%) prefix. If the percent symbol is present, then
+syncpdb interprets the filename and line number as referring to the document,
+rather than to the code that is executed. It will translate the filename and
+line number to the corresponding code equivalents, and then pass these to the
+standard pdb internals. For example, the pdb command `list 50` would list
+the code that is being executed, centered around line 50. syncpdb allows the
+command `list %10`, which would list the code that is being executed,
+centered around the code that came from line 10 in the main document. (If no
+file name is given, then the main document is assumed.) If the code instead
+came from an inputed file `input.tex`, then `list %input.tex:10` could be
+used.
+
+ * * *
+
+The synchronization is accomplished via a synchronization file with
+the extension .syncdb. It should be located in the same directory as the
+code it synchronizes, and should have the same name as the code, with the
+addition of the .syncdb extension. For example, `code.py` would have
+`code.py.syncdb`. Currently, the .syncdb must be encoded in UTF8. The file
+has the following format. For each chunk of code extracted from a document
+for execution, the file contains a line with the following information:
+ ```
+ <code filename>,<code lineno>,<doc filename>,<doc lineno>,<chunk length>
+ ```/
+The first line of the file must be
+ ```
+ <main code filename>,,<main doc filename>,,
+ ```/
+All code filenames should be given relative to the main code filename.
+
+The .syncdb format is thus a comma-separated value (csv) format. The
+elements are defined as follows:
+ * <code filename>: The name of the code file in which the current chunk
+ of user code is inserted. This should be double-quoted if it contains
+ commas.
+ * <code lineno>: The line of the executed code on which the current chunk
+ of user code begins.
+ * <doc filename>: The name of the document file from which the current
+ chunk of user code was extracted. This should be double-quoted if it
+ contains commas.
+ * <doc lineno>: The line number in the document file where the chunk of
+ user code begins.
+ * <chunk length>: The length of the chunk of code (number of lines).
+This information is sufficient for calculating the relationship of each line
+in the code that is executed (and that originated in the document, not a
+template) with a line in the document from which the code was extracted.
+
+As an example, suppose that a document main.tex contains 10 lines of Python
+code, beginning on line 50, that are to be executed. When this code is
+inserted into a template for execution, it begins on line 75 of the code that
+is actually executed. In this case, the .syncdb file would contain the
+following information for this chunk of code.
+ ```
+ code.py,75,main.tex,50,10
+ ```/
+
+While the .syncdb format is currently only intended for simple literate
+programming-type systems, in the future it may be extended for more complex
+cases in which a chunk of code may be substituted into another chunk, perhaps
+with variable replacement. In such cases, the `<doc filename>, <doc lineno>,`
+may be repeated for each location with a connection to a given line of code,
+allowing a complete traceback of the code's origin to be assembled. The
+rightmost `<doc filename>, <doc lineno>,` should be the most specific of all
+such pairs.
+
+ * * *
+
+This code is based on pdb.py and bdb.py from the Python standard library.
+It subclasses Pdb(), overwriting a number of methods to provide
+synchronization between the code that is executed and the file from which it
+was extracted. It also provides a number functions adapted from pdb.py to
+govern execution. Most of the modifications to the pdb and bdb sources are
+wrapped in the pair of comments `# SPdb` and `# /SPdb`.
+
+This code is compatible with both Python 2 and Python 3. It is based on the
+pdb.py and bdb.py from Python 2.7.5 and Python 3.3.2. Several minor
+modifications were required to get the code from both sources to play nicely
+within the same file.
+
+Licensed under the BSD 3-Clause License
+
+Copyright (c) 2014, Geoffrey M. Poore
+'''
+
+import sys
+import os
+import pdb
+import bdb
+import linecache
+if sys.version_info.major == 2:
+ from io import open
+ from repr import Repr
+else:
+ import inspect
+ import dis
+import re
+from collections import defaultdict, namedtuple
+import traceback
+
+
+__version__ = '0.1'
+
+
+__all__ = ["run", "pm", "SyncPdb", "runeval", "runctx", "runcall", "set_trace",
+ "post_mortem", "help"]
+
+
+if sys.version_info.major == 2:
+
+ class Restart(Exception):
+ """Causes a debugger to be restarted for the debugged python program."""
+ pass
+
+ _repr = Repr()
+ _repr.maxstring = 200
+ _saferepr = _repr.repr
+
+ def find_function(funcname, filename):
+ cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname))
+ try:
+ fp = open(filename)
+ except IOError:
+ return None
+ # consumer of this info expects the first line to be 1
+ lineno = 1
+ answer = None
+ while 1:
+ line = fp.readline()
+ if line == '':
+ break
+ if cre.match(line):
+ answer = funcname, filename, lineno
+ break
+ lineno = lineno + 1
+ fp.close()
+ return answer
+
+ line_prefix = '\n-> '
+
+else:
+
+ class Restart(Exception):
+ """Causes a debugger to be restarted for the debugged python program."""
+ pass
+
+ def find_function(funcname, filename):
+ cre = re.compile(r'def\s+%s\s*[(]' % re.escape(funcname))
+ try:
+ fp = open(filename)
+ except IOError:
+ return None
+ # consumer of this info expects the first line to be 1
+ lineno = 1
+ answer = None
+ while True:
+ line = fp.readline()
+ if line == '':
+ break
+ if cre.match(line):
+ answer = funcname, filename, lineno
+ break
+ lineno += 1
+ fp.close()
+ return answer
+
+ def getsourcelines(obj):
+ lines, lineno = inspect.findsource(obj)
+ if inspect.isframe(obj) and obj.f_globals is obj.f_locals:
+ # must be a module frame: do not try to cut a block out of it
+ return lines, 1
+ elif inspect.ismodule(obj):
+ return lines, 1
+ return inspect.getblock(lines[lineno:]), lineno+1
+
+ def lasti2lineno(code, lasti):
+ linestarts = list(dis.findlinestarts(code))
+ linestarts.reverse()
+ for i, lineno in linestarts:
+ if lasti >= i:
+ return lineno
+ return 0
+
+ class _rstr(str):
+ """String that doesn't quote its repr."""
+ def __repr__(self):
+ return self
+
+ line_prefix = '\n-> '
+
+
+
+Sync = namedtuple('Sync', ['file', 'line'])
+def defaultsync():
+ return Sync(None, None)
+
+
+class SyncPdb(pdb.Pdb):
+ '''
+ Methods that need to be redefined from Pdb for Python 2
+ + do_list()
+ + format_stack_entry() (bdb)
+ + do_break()
+ x clear_break() (bdb)
+ + bpprint() (bdb)
+ + do_jump()
+ + do_clear()
+ + format_stack_entry() (bdb)
+
+ Methods that need to be redefined from Pdb for Python 3
+ + do_break()
+ + _print_lines()
+ x clear_break() (bdb)
+ + bpformat() (bdb)
+ + do_jump()
+ + do_list()
+ + do_long_list()
+ + do_source()
+ + format_stack_entry() (bdb)
+ + do_clear()
+ '''
+ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
+ syncdb=None):
+ pdb.Pdb.__init__(self, completekey=completekey, stdin=stdin,
+ stdout=stdout, skip=skip)
+ self._load_syncdb()
+
+ _code_to_doc_dict = defaultdict(lambda: defaultdict(defaultsync))
+ _doc_to_code_dict = defaultdict(lambda: defaultdict(defaultsync))
+
+ _syncdb_pattern = re.compile('([^"]*|"[^"]*"),(.*?),([^"]*|"[^"]*"),(.*?),(.*?)\n')
+
+ def _load_syncdb(self):
+ syncdb_fname = sys.argv[0] + '.syncdb'
+ if os.path.isfile(syncdb_fname):
+ f = open(syncdb_fname, 'r', encoding='utf8')
+ data = f.readlines()
+ f.close()
+ main_code_fname, main_doc_fname = [x.strip('"') for x in self._syncdb_pattern.match(data[0]).groups()[0:3:2]]
+ self.main_code_fname = main_code_fname
+ self.main_doc_fname = main_doc_fname
+ # If the main code file isn't being executed from its own
+ # directory, then we will need to correct all code file paths
+ # for this.
+ self.current_code_path, self.current_code_fname = os.path.split(sys.argv[0])
+ # Check to make sure syncdb is compatible. It could have been
+ # copied under another name in an attempt to reuse it with
+ # another, related script. But that doesn't work, at least
+ # currently.
+ if self.current_code_fname != self.main_code_fname:
+ sys.exit('The synchonization file is only compatible with "{0}", not "{1}"'.format(self.main_code_fname, self.current_code_fname))
+ for line in data[1:]:
+ code_fname, code_start_lineno, doc_fname, doc_start_lineno, input_length = self._syncdb_pattern.match(line).groups()
+ code_fname = os.path.normcase(code_fname.strip('"').replace('\\', '/'))
+ doc_fname = doc_fname.strip('"')
+ code_start_lineno = int(code_start_lineno)
+ doc_start_lineno = int(doc_start_lineno)
+ input_length = int(input_length)
+ code_fname_key = os.path.join(self.current_code_path, code_fname)
+ code_fname_key_full = os.path.normcase(os.path.abspath(code_fname_key))
+ is_main_code = code_fname == main_code_fname
+ is_main_doc = doc_fname == main_doc_fname
+ for n in range(0, input_length):
+ s = Sync(doc_fname, doc_start_lineno + n)
+ self._code_to_doc_dict[code_fname_key][code_start_lineno + n] = s
+ self._code_to_doc_dict[code_fname_key_full][code_start_lineno + n] = s
+ if is_main_code:
+ self._code_to_doc_dict[''][code_start_lineno + n] = s
+ # When there are multiple sources of code in a
+ # single line of the document, we want to use the
+ # first one
+ if doc_start_lineno + n not in self._doc_to_code_dict[doc_fname]:
+ s = Sync(code_fname_key, code_start_lineno + n)
+ self._doc_to_code_dict[doc_fname][doc_start_lineno + n] = s
+ if is_main_doc:
+ self._doc_to_code_dict[''][doc_start_lineno + n] = s
+ else:
+ sys.exit('Could not find synchronization file "{0}"'.format(syncdb_fname))
+
+ def code_to_doc(self, code_fname, code_lineno):
+ if code_fname in self._code_to_doc_dict:
+ if self._code_to_doc_dict[code_fname]:
+ return self._code_to_doc_dict[code_fname][code_lineno]
+ else:
+ return defaultsync()
+ else:
+ if code_fname not in self._code_to_doc_dict:
+ self._code_to_doc_dict[code_fname] = None
+ return self.code_to_doc(code_fname, code_lineno)
+
+ def doc_to_code(self, doc_fname, doc_lineno):
+ if doc_fname in self._doc_to_code_dict:
+ if self._doc_to_code_dict[doc_fname]:
+ return self._doc_to_code_dict[doc_fname][doc_lineno]
+ else:
+ return defaultsync()
+ else:
+ if doc_fname not in self._doc_to_code_dict:
+ self._doc_to_code_dict[doc_fname] = None
+ return self.doc_to_code(doc_fname, doc_lineno)
+
+ _line_numbering_offset = 5
+
+ def _format_line_main_doc(self, s, l):
+ return '{0} '.format(l).rjust(self._line_numbering_offset) + s
+
+ def _format_line_other_doc(self, s, l):
+ return '{0} '.format(l).rjust(self._line_numbering_offset) + s
+
+ def _format_line_no_doc(self, s):
+ return ' '*self._line_numbering_offset + s
+
+ _eof_template = ' '*(_line_numbering_offset-1) + '[EOF]'
+
+ _last_doc_fname = None
+
+ _doc_switch_template = ' {0}:'
+
+ _doc_command_char = '%'
+ _doc_command_char_stripset = ' {0}'.format(_doc_command_char)
+
+
+ if sys.version_info.major == 2:
+
+ def bpprint(self, bp, out=None):
+ '''
+ Replacement for Bdb.bpprint()
+ '''
+ if out is None:
+ out = sys.stdout
+ if bp.temporary:
+ disp = 'del '
+ else:
+ disp = 'keep '
+ if bp.enabled:
+ disp = disp + 'yes '
+ else:
+ disp = disp + 'no '
+ if bp.doc_file is None:
+ print >>out, '%-4dbreakpoint %s at %s:%d' % (bp.number, disp,
+ bp.file, bp.line)
+ else:
+ print >>out, '%-4dbreakpoint %s at %s:%d (%s:%d)' % (bp.number, disp,
+ bp.file, bp.line,
+ bp.doc_file, bp.doc_line)
+ if bp.cond:
+ print >>out, '\tstop only if %s' % (bp.cond,)
+ if bp.ignore:
+ print >>out, '\tignore next %d hits' % (bp.ignore)
+ if (bp.hits):
+ if (bp.hits > 1): ss = 's'
+ else: ss = ''
+ print >>out, ('\tbreakpoint already hit %d time%s' %
+ (bp.hits, ss))
+
+
+ def do_break(self, arg, temporary = 0):
+ # break [ ([filename:]lineno | function) [, "condition"] ]
+ if not arg:
+ if self.breaks: # There's at least one
+ print >>self.stdout, "Num Type Disp Enb Where"
+ for bp in bdb.Breakpoint.bpbynumber:
+ if bp:
+ # SPdb
+ self.bpprint(bp, self.stdout)
+ #bp.bpprint(self.stdout)
+ # /SPdb
+ return
+ # parse arguments; comma has lowest precedence
+ # and cannot occur in filename
+ filename = None
+ lineno = None
+ cond = None
+ comma = arg.find(',')
+ if comma > 0:
+ # parse stuff after comma: "condition"
+ cond = arg[comma+1:].lstrip()
+ arg = arg[:comma].lstrip()
+ # SPdb
+ arg = arg.strip()
+ if arg.startswith(self._doc_command_char):
+ convert = True
+ arg2 = arg.lstrip(self._doc_command_char_stripset)
+ else:
+ convert = False
+ arg2 = arg
+ # parse stuff before comma: [filename:]lineno | function
+ colon = arg2.rfind(':')
+ funcname = None
+ if colon >= 0:
+ filename = arg2[:colon].rstrip()
+ arg2 = arg2[colon+1:].lstrip()
+ try:
+ lineno = int(arg2)
+ except ValueError:
+ print >>self.stdout, '*** Bad lineno:', arg2
+ return
+ if convert:
+ filename, lineno = self.doc_to_code(filename, lineno)
+ filename = os.path.split(filename)[1]
+ lineno = int(lineno)
+ f = self.lookupmodule(filename)
+ if not f:
+ print >>self.stdout, '*** ', repr(filename),
+ print >>self.stdout, 'not found from sys.path'
+ return
+ else:
+ filename = f
+ # SPdb
+ #arg = arg[colon+1:].lstrip()
+ #try:
+ # lineno = int(arg)
+ #except ValueError, msg:
+ # print >>self.stdout, '*** Bad lineno:', arg
+ # return
+ # /SPdb
+ else:
+ # no colon; can be lineno or function
+ try:
+ lineno = int(arg2)
+ if convert:
+ lineno = int(self.doc_to_code('', lineno).line)
+ except ValueError:
+ try:
+ func = eval(arg2,
+ self.curframe.f_globals,
+ self.curframe_locals)
+ except:
+ func = arg2
+ try:
+ if hasattr(func, 'im_func'):
+ func = func.im_func
+ code = func.func_code
+ #use co_name to identify the bkpt (function names
+ #could be aliased, but co_name is invariant)
+ funcname = code.co_name
+ lineno = code.co_firstlineno
+ filename = code.co_filename
+ except:
+ # last thing to try
+ (ok, filename, ln) = self.lineinfo(arg2)
+ if not ok:
+ print >>self.stdout, '*** The specified object',
+ print >>self.stdout, repr(arg2),
+ print >>self.stdout, 'is not a function'
+ print >>self.stdout, 'or was not found along sys.path.'
+ return
+ funcname = ok # ok contains a function name
+ lineno = int(ln)
+ # /SPdb
+ if not filename:
+ filename = self.defaultFile()
+ # Check for reasonable breakpoint
+ line = self.checkline(filename, lineno)
+ if line:
+ # now set the break point
+ err = self.set_break(filename, line, temporary, cond, funcname)
+ if err: print >>self.stdout, '***', err
+ else:
+ bp = self.get_breaks(filename, line)[-1]
+ # SPdb
+ sync = self.code_to_doc(filename, lineno)
+ if sync == (None, None):
+ print >>self.stdout, "Breakpoint %d at %s:%d" % (bp.number,
+ bp.file,
+ bp.line)
+ bp.doc_file = None
+ bp.doc_line = None
+ else:
+ print >>self.stdout, "Breakpoint %d at %s:%d (%s:%d)" % (bp.number,
+ bp.file,
+ bp.line,
+ sync.file,
+ sync.line)
+ bp.doc_file = sync.file
+ bp.doc_line = sync.line
+ # /SPdb
+
+ do_b = do_break
+
+
+ def do_clear(self, arg):
+ """Three possibilities, tried in this order:
+ clear -> clear all breaks, ask for confirmation
+ clear file:lineno -> clear all breaks at file:lineno
+ clear bpno bpno ... -> clear breakpoints by number"""
+ if not arg:
+ try:
+ reply = raw_input('Clear all breaks? ')
+ except EOFError:
+ reply = 'no'
+ reply = reply.strip().lower()
+ if reply in ('y', 'yes'):
+ self.clear_all_breaks()
+ return
+ if ':' in arg:
+ # Make sure it works for "clear C:\foo\bar.py:12"
+ i = arg.rfind(':')
+ # SPdb
+ filename = arg[:i].strip()
+ arg = arg[i+1:]
+ if filename.startswith(self._doc_command_char):
+ filename = filename.lstrip(self._doc_command_char_stripset)
+ filename, arg = self.doc_to_code(filename, int(arg))
+ # /SPdb
+ try:
+ lineno = int(arg)
+ except ValueError:
+ err = "Invalid line number (%s)" % arg
+ else:
+ err = self.clear_break(filename, lineno)
+ if err: print >>self.stdout, '***', err
+ return
+ numberlist = arg.split()
+ for i in numberlist:
+ try:
+ i = int(i)
+ except ValueError:
+ print >>self.stdout, 'Breakpoint index %r is not a number' % i
+ continue
+
+ if not (0 <= i < len(bdb.Breakpoint.bpbynumber)):
+ print >>self.stdout, 'No breakpoint numbered', i
+ continue
+ err = self.clear_bpbynumber(i)
+ if err:
+ print >>self.stdout, '***', err
+ else:
+ print >>self.stdout, 'Deleted breakpoint', i
+
+ do_cl = do_clear # 'c' is already an abbreviation for 'continue'
+
+
+ def do_jump(self, arg):
+ if self.curindex + 1 != len(self.stack):
+ print >>self.stdout, "*** You can only jump within the bottom frame"
+ return
+ # SPdb
+ if arg.startswith(self._doc_command_char):
+ convert = True
+ if ':' in arg:
+ doc_fname, arg = arg.lstrip(self._doc_command_char_stripset).split(':', 1)
+ else:
+ doc_fname = ''
+ arg = arg.lstrip(self._doc_command_char_stripset)
+ else:
+ convert = False
+ # /SPdb
+ try:
+ arg = int(arg)
+ # SPdb
+ if convert:
+ arg = int(self.doc_to_code(doc_fname, arg).line)
+ # /SPdb
+ except ValueError:
+ print >>self.stdout, "*** The 'jump' command requires a line number."
+ else:
+ try:
+ # Do the jump, fix up our copy of the stack, and display the
+ # new position
+ self.curframe.f_lineno = arg
+ self.stack[self.curindex] = self.stack[self.curindex][0], arg
+ self.print_stack_entry(self.stack[self.curindex])
+ # SPdb
+ except ValueError as e:
+ print >>self.stdout, '*** Jump failed:', e
+ # /SPdb
+
+ do_j = do_jump
+
+
+ def do_list(self, arg):
+ self.lastcmd = 'list'
+ last = None
+ if arg:
+ # SPdb
+ arg = arg.strip()
+ if arg.startswith(self._doc_command_char):
+ convert = True
+ if ':' in arg:
+ doc_fname, arg2 = arg.lstrip(self._doc_command_char_stripset).split(':', 1)
+ else:
+ doc_fname = ''
+ arg2 = arg.lstrip(self._doc_command_char_stripset)
+ else:
+ convert = False
+ arg2 = arg
+ # /SPdb
+ try:
+ # SPdb
+ x = eval(arg2, {}, {})
+ # /SPdb
+ if type(x) == type(()):
+ first, last = x
+ first = int(first)
+ last = int(last)
+ # SPdb
+ if convert:
+ first = int(self.doc_to_code(doc_fname, first).line)
+ last = int(self.doc_to_code(doc_fname, last).line)
+ # /SPdb
+ if last < first:
+ # Assume it's a count
+ last = first + last
+ else:
+ # SPdb
+ first = int(x)
+ if convert:
+ first = int(self.doc_to_code(doc_fname, first).line)
+ first = max(1, first - 5)
+ # /SPdb
+ except:
+ print >>self.stdout, '*** Error in argument:', repr(arg)
+ return
+ elif self.lineno is None:
+ first = max(1, self.curframe.f_lineno - 5)
+ else:
+ first = self.lineno + 1
+ if last is None:
+ last = first + 10
+ filename = self.curframe.f_code.co_filename
+ breaklist = self.get_file_breaks(filename)
+ try:
+ # SPdb
+ self._last_doc_fname = None
+ # /SPdb
+ for lineno in range(first, last+1):
+ line = linecache.getline(filename, lineno,
+ self.curframe.f_globals)
+ if not line:
+ print >>self.stdout, self._eof_template
+ break
+ else:
+ s = repr(lineno).rjust(3)
+ if len(s) < 4: s = s + ' '
+ if lineno in breaklist: s = s + 'B'
+ else: s = s + ' '
+ if lineno == self.curframe.f_lineno:
+ s = s + '->'
+ # SPdb
+ f, l = self.code_to_doc(filename, lineno)
+ if f == self.main_doc_fname:
+ s = self._format_line_main_doc(s, l)
+ elif f:
+ s = self._format_line_other_doc(s, l)
+ else:
+ s = self._format_line_no_doc(s)
+ if f != self._last_doc_fname:
+ self._last_doc_fname = f
+ if f is not None:
+ print(self._doc_switch_template.format(f))
+ # /SPdb
+ print >>self.stdout, s + '\t' + line,
+ self.lineno = lineno
+ except KeyboardInterrupt:
+ pass
+
+ do_l = do_list
+
+
+ def format_stack_entry(self, frame_lineno, lprefix=': '):
+ import linecache, repr
+ frame, lineno = frame_lineno
+ filename = self.canonic(frame.f_code.co_filename)
+ s = '%s(%r)' % (filename, lineno)
+ if frame.f_code.co_name:
+ s = s + frame.f_code.co_name
+ else:
+ s = s + "<lambda>"
+ if '__args__' in frame.f_locals:
+ args = frame.f_locals['__args__']
+ else:
+ args = None
+ if args:
+ s = s + repr.repr(args)
+ else:
+ s = s + '()'
+ if '__return__' in frame.f_locals:
+ rv = frame.f_locals['__return__']
+ s = s + '->'
+ s = s + repr.repr(rv)
+ line = linecache.getline(filename, lineno, frame.f_globals)
+ # SPdb
+ sync = self.code_to_doc(frame.f_code.co_filename, lineno)
+ if sync == (None, None):
+ sync_info = ''
+ else:
+ sync_info = ' ({0}:{1})'.format(sync.file, sync.line)
+ if line: s = s + sync_info + lprefix + line.strip()
+ # /SPdb
+ return s
+
+
+ else:
+
+ def bpformat(self, bp):
+ if bp.temporary:
+ disp = 'del '
+ else:
+ disp = 'keep '
+ if bp.enabled:
+ disp = disp + 'yes '
+ else:
+ disp = disp + 'no '
+ # SPdb
+ if bp.doc_file is None:
+ ret = '%-4dbreakpoint %s at %s:%d' % (bp.number, disp,
+ bp.file, bp.line)
+ else:
+ ret = '%-4dbreakpoint %s at %s:%d (%s:%d)' % (bp.number, disp,
+ bp.file, bp.line,
+ bp.doc_file, bp.doc_line)
+ # /SPdb
+ if bp.cond:
+ ret += '\n\tstop only if %s' % (bp.cond,)
+ if bp.ignore:
+ ret += '\n\tignore next %d hits' % (bp.ignore,)
+ if bp.hits:
+ if bp.hits > 1:
+ ss = 's'
+ else:
+ ss = ''
+ ret += '\n\tbreakpoint already hit %d time%s' % (bp.hits, ss)
+ return ret
+
+
+ def do_break(self, arg, temporary = 0):
+ """b(reak) [ ([filename:]lineno | function) [, condition] ]
+ Without argument, list all breaks.
+
+ With a line number argument, set a break at this line in the
+ current file. With a function name, set a break at the first
+ executable line of that function. If a second argument is
+ present, it is a string specifying an expression which must
+ evaluate to true before the breakpoint is honored.
+
+ The line number may be prefixed with a filename and a colon,
+ to specify a breakpoint in another file (probably one that
+ hasn't been loaded yet). The file is searched for on
+ sys.path; the .py suffix may be omitted.
+ """
+ if not arg:
+ if self.breaks: # There's at least one
+ self.message("Num Type Disp Enb Where")
+ for bp in bdb.Breakpoint.bpbynumber:
+ if bp:
+ self.message(self.bpformat(bp))
+ return
+ # parse arguments; comma has lowest precedence
+ # and cannot occur in filename
+ filename = None
+ lineno = None
+ cond = None
+ comma = arg.find(',')
+ if comma > 0:
+ # parse stuff after comma: "condition"
+ cond = arg[comma+1:].lstrip()
+ arg = arg[:comma].rstrip()
+ # SPdb
+ arg = arg.strip()
+ if arg.startswith(self._doc_command_char):
+ convert = True
+ arg2 = arg.lstrip(self._doc_command_char_stripset)
+ else:
+ convert = False
+ arg2 = arg
+ # parse stuff before comma: [filename:]lineno | function
+ colon = arg2.rfind(':')
+ funcname = None
+ if colon >= 0:
+ filename = arg2[:colon].rstrip()
+ arg2 = arg2[colon+1:].lstrip()
+ try:
+ lineno = int(arg2)
+ except ValueError:
+ self.error('Bad lineno: %s' % arg2)
+ return
+ if convert:
+ filename, lineno = self.doc_to_code(filename, lineno)
+ filename = os.path.split(filename)[1]
+ lineno = int(lineno)
+ f = self.lookupmodule(filename)
+ if not f:
+ self.error('%r not found from sys.path' % filename)
+ return
+ else:
+ filename = f
+ # SPdb
+ #arg = arg[colon+1:].lstrip()
+ #try:
+ # lineno = int(arg)
+ #except ValueError:
+ # self.error('Bad lineno: %s' % arg)
+ # return
+ # SPdb
+ else:
+ # no colon; can be lineno or function
+ try:
+ lineno = int(arg2)
+ if convert:
+ lineno = int(self.doc_to_code('', lineno).line)
+ except ValueError:
+ try:
+ func = eval(arg2,
+ self.curframe.f_globals,
+ self.curframe_locals)
+ except:
+ func = arg2
+ try:
+ if hasattr(func, '__func__'):
+ func = func.__func__
+ code = func.__code__
+ #use co_name to identify the bkpt (function names
+ #could be aliased, but co_name is invariant)
+ funcname = code.co_name
+ lineno = code.co_firstlineno
+ filename = code.co_filename
+ except:
+ # last thing to try
+ (ok, filename, ln) = self.lineinfo(arg2)
+ if not ok:
+ self.error('The specified object %r is not a function '
+ 'or was not found along sys.path.' % arg2)
+ return
+ funcname = ok # ok contains a function name
+ lineno = int(ln)
+ # /SPdb
+ if not filename:
+ filename = self.defaultFile()
+ # Check for reasonable breakpoint
+ line = self.checkline(filename, lineno)
+ if line:
+ # now set the break point
+ err = self.set_break(filename, line, temporary, cond, funcname)
+ if err:
+ self.error(err, file=self.stdout)
+ else:
+ bp = self.get_breaks(filename, line)[-1]
+ # SPdb
+ sync = self.code_to_doc(filename, lineno)
+ if sync == (None, None):
+ self.message("Breakpoint %d at %s:%d" %
+ (bp.number, bp.file, bp.line))
+ bp.doc_file = None
+ bp.doc_line = None
+ else:
+ self.message("Breakpoint %d at %s:%d (%s:%d)" %
+ (bp.number, bp.file, bp.line,
+ sync.file, sync.line))
+ bp.doc_file = sync.file
+ bp.doc_line = sync.line
+ # /SPdb
+
+ do_b = do_break
+
+
+ def do_clear(self, arg):
+ """cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]]
+ With a space separated list of breakpoint numbers, clear
+ those breakpoints. Without argument, clear all breaks (but
+ first ask confirmation). With a filename:lineno argument,
+ clear all breaks at that line in that file.
+ """
+ if not arg:
+ try:
+ reply = input('Clear all breaks? ')
+ except EOFError:
+ reply = 'no'
+ reply = reply.strip().lower()
+ if reply in ('y', 'yes'):
+ bplist = [bp for bp in bdb.Breakpoint.bpbynumber if bp]
+ self.clear_all_breaks()
+ for bp in bplist:
+ self.message('Deleted %s' % bp)
+ return
+ if ':' in arg:
+ # Make sure it works for "clear C:\foo\bar.py:12"
+ i = arg.rfind(':')
+ # SPdb
+ filename = arg[:i].strip()
+ arg = arg[i+1:]
+ if filename.startswith(self._doc_command_char):
+ filename = filename.lstrip(self._doc_command_char_stripset)
+ filename, arg = self.doc_to_code(filename, int(arg))
+ # /SPdb
+ try:
+ lineno = int(arg)
+ except ValueError:
+ err = "Invalid line number (%s)" % arg
+ else:
+ bplist = self.get_breaks(filename, lineno)
+ err = self.clear_break(filename, lineno)
+ if err:
+ self.error(err)
+ else:
+ for bp in bplist:
+ self.message('Deleted %s' % bp)
+ return
+ numberlist = arg.split()
+ for i in numberlist:
+ try:
+ bp = self.get_bpbynumber(i)
+ except ValueError as err:
+ self.error(err)
+ else:
+ self.clear_bpbynumber(i)
+ self.message('Deleted %s' % bp)
+
+ do_cl = do_clear # 'c' is already an abbreviation for 'continue'
+
+
+ def do_jump(self, arg):
+ """j(ump) lineno
+ Set the next line that will be executed. Only available in
+ the bottom-most frame. This lets you jump back and execute
+ code again, or jump forward to skip code that you don't want
+ to run.
+
+ It should be noted that not all jumps are allowed -- for
+ instance it is not possible to jump into the middle of a
+ for loop or out of a finally clause.
+ """
+ if self.curindex + 1 != len(self.stack):
+ self.error('You can only jump within the bottom frame')
+ return
+ # SPdb
+ if arg.startswith(self._doc_command_char):
+ convert = True
+ if ':' in arg:
+ doc_fname, arg = arg.lstrip(self._doc_command_char_stripset).split(':', 1)
+ else:
+ doc_fname = ''
+ arg = arg.lstrip(self._doc_command_char_stripset)
+ else:
+ convert = False
+ # /SPdb
+ try:
+ arg = int(arg)
+ # SPdb
+ if convert:
+ arg = int(self.doc_to_code(doc_fname, arg).line)
+ # /SPdb
+ except ValueError:
+ self.error("The 'jump' command requires a line number")
+ else:
+ try:
+ # Do the jump, fix up our copy of the stack, and display the
+ # new position
+ self.curframe.f_lineno = arg
+ self.stack[self.curindex] = self.stack[self.curindex][0], arg
+ self.print_stack_entry(self.stack[self.curindex])
+ except ValueError as e:
+ self.error('Jump failed: %s' % e)
+
+ do_j = do_jump
+
+
+ def do_list(self, arg):
+ """l(ist) [first [,last] | .]
+
+ List source code for the current file. Without arguments,
+ list 11 lines around the current line or continue the previous
+ listing. With . as argument, list 11 lines around the current
+ line. With one argument, list 11 lines starting at that line.
+ With two arguments, list the given range; if the second
+ argument is less than the first, it is a count.
+
+ The current line in the current frame is indicated by "->".
+ If an exception is being debugged, the line where the
+ exception was originally raised or propagated is indicated by
+ ">>", if it differs from the current line.
+ """
+ self.lastcmd = 'list'
+ last = None
+ if arg and arg != '.':
+ try:
+ # SPdb
+ arg = arg.strip()
+ if arg.startswith(self._doc_command_char):
+ convert = True
+ if ':' in arg:
+ doc_fname, arg2 = arg.lstrip(self._doc_command_char_stripset).split(':', 1)
+ else:
+ doc_fname = ''
+ arg2 = arg.lstrip(self._doc_command_char_stripset)
+ else:
+ convert = False
+ arg2 = arg
+ if ',' in arg2:
+ first, last = arg2.split(',')
+ first = int(first.strip())
+ last = int(last.strip())
+ if convert:
+ first = int(self.doc_to_code(doc_fname, first).line)
+ last = int(self.doc_to_code(doc_fname, last).line)
+ if last < first:
+ # assume it's a count
+ last = first + last
+ else:
+ first = int(arg2.strip())
+ if convert:
+ first = int(self.doc_to_code(doc_fname, first).line)
+ first = max(1, first - 5)
+ # /SPdb
+ except ValueError:
+ self.error('Error in argument: %r' % arg)
+ return
+ elif self.lineno is None or arg == '.':
+ first = max(1, self.curframe.f_lineno - 5)
+ else:
+ first = self.lineno + 1
+ if last is None:
+ last = first + 10
+ filename = self.curframe.f_code.co_filename
+ breaklist = self.get_file_breaks(filename)
+ try:
+ lines = linecache.getlines(filename, self.curframe.f_globals)
+ # SPdb
+ self._print_lines(filename, lines[first-1:last], first, last,
+ breaklist, self.curframe)
+ # /SPdb
+ self.lineno = min(last, len(lines))
+ # SPdb
+ #if len(lines) < last:
+ # self.message('[EOF]')
+ # /SPdb
+ except KeyboardInterrupt:
+ pass
+
+ do_l = do_list
+
+
+ def do_longlist(self, arg):
+ """longlist | ll
+ List the whole source code for the current function or frame.
+ """
+ filename = self.curframe.f_code.co_filename
+ breaklist = self.get_file_breaks(filename)
+ try:
+ lines, lineno = getsourcelines(self.curframe)
+ except IOError as err:
+ self.error(err)
+ return
+ # SPdb
+ self._print_lines(filename, lines, lineno, lineno + len(lines) - 1,
+ breaklist, self.curframe)
+ # /SPdb
+
+ do_ll = do_longlist
+
+
+ def do_source(self, arg):
+ """source expression
+ Try to get source code for the given object and display it.
+ """
+ try:
+ obj = self._getval(arg)
+ except:
+ return
+ try:
+ lines, lineno = getsourcelines(obj)
+ except (IOError, TypeError) as err:
+ self.error(err)
+ return
+ self._print_lines(lines, lineno)
+
+
+ # SPdb added filename, last args; renames start -> first # /SPdb
+ def _print_lines(self, filename, lines, first, last, breaks=(), frame=None):
+ """Print a range of lines."""
+ if frame:
+ current_lineno = frame.f_lineno
+ exc_lineno = self.tb_lineno.get(frame, -1)
+ else:
+ current_lineno = exc_lineno = -1
+ # SPdb
+ self._last_doc_fname = None
+ # /Spdb
+ for lineno, line in enumerate(lines, first):
+ s = str(lineno).rjust(3)
+ if len(s) < 4:
+ s += ' '
+ if lineno in breaks:
+ s += 'B'
+ else:
+ s += ' '
+ if lineno == current_lineno:
+ s += '->'
+ elif lineno == exc_lineno:
+ s += '>>'
+ # SPdb
+ f, l = self.code_to_doc(filename, lineno)
+ if f == self.main_doc_fname:
+ s = self._format_line_main_doc(s, l)
+ elif f:
+ s = self._format_line_other_doc(s, l)
+ else:
+ s = self._format_line_no_doc(s)
+ if f != self._last_doc_fname:
+ self._last_doc_fname = f
+ if f is not None:
+ self.message(self._doc_switch_template.format(f))
+ # /SPdb
+ self.message(s + '\t' + line.rstrip())
+ # SPdb
+ if len(lines) < last - first + 1:
+ self.message(self._eof_template)
+ # /SPdb
+
+
+ def format_stack_entry(self, frame_lineno, lprefix=': '):
+ import linecache, reprlib
+ frame, lineno = frame_lineno
+ filename = self.canonic(frame.f_code.co_filename)
+ s = '%s(%r)' % (filename, lineno)
+ if frame.f_code.co_name:
+ s += frame.f_code.co_name
+ else:
+ s += "<lambda>"
+ if '__args__' in frame.f_locals:
+ args = frame.f_locals['__args__']
+ else:
+ args = None
+ if args:
+ s += reprlib.repr(args)
+ else:
+ s += '()'
+ if '__return__' in frame.f_locals:
+ rv = frame.f_locals['__return__']
+ s += '->'
+ s += reprlib.repr(rv)
+ line = linecache.getline(filename, lineno, frame.f_globals)
+ # SPdb
+ sync = self.code_to_doc(frame.f_code.co_filename, lineno)
+ if sync == (None, None):
+ sync_info = ''
+ else:
+ sync_info = ' ({0}:{1})'.format(sync.file, sync.line)
+ # /SPdb
+ if line:
+ # SPdb
+ s += sync_info
+ # /Spdb
+ s += lprefix + line.strip()
+ return s
+
+
+
+
+if sys.version_info.major == 2:
+
+ # Simplified interface
+
+ def run(statement, globals=None, locals=None):
+ SyncPdb().run(statement, globals, locals)
+
+ def runeval(expression, globals=None, locals=None):
+ return SyncPdb().runeval(expression, globals, locals)
+
+ def runctx(statement, globals, locals):
+ # B/W compatibility
+ run(statement, globals, locals)
+
+ def runcall(*args, **kwds):
+ return SyncPdb().runcall(*args, **kwds)
+
+ def set_trace():
+ SyncPdb().set_trace(sys._getframe().f_back)
+
+ # Post-Mortem interface
+
+ def post_mortem(t=None):
+ # handling the default
+ if t is None:
+ # sys.exc_info() returns (type, value, traceback) if an exception is
+ # being handled, otherwise it returns None
+ t = sys.exc_info()[2]
+ if t is None:
+ raise ValueError("A valid traceback must be passed if no "
+ "exception is being handled")
+
+ p = SyncPdb()
+ p.reset()
+ p.interaction(None, t)
+
+ def pm():
+ post_mortem(sys.last_traceback)
+
+
+ # Main program for testing
+
+ TESTCMD = 'import x; x.main()'
+
+ def test():
+ run(TESTCMD)
+
+ # print help
+ def help():
+ for dirname in sys.path:
+ fullname = os.path.join(dirname, 'pdb.doc')
+ if os.path.exists(fullname):
+ sts = os.system('${PAGER-more} '+fullname)
+ # SPdb
+ if sts: print('*** Pager exit status: {0}'.format(sts))
+ # /SPdb
+ break
+ else:
+ # SPdb
+ print('Sorry, can\'t find the help file "pdb.doc" along the Python search path')
+ # /SPdb
+
+else:
+
+ # Collect all command help into docstring, if not run with -OO
+
+ if __doc__ is not None:
+ # unfortunately we can't guess this order from the class definition
+ _help_order = [
+ 'help', 'where', 'down', 'up', 'break', 'tbreak', 'clear', 'disable',
+ 'enable', 'ignore', 'condition', 'commands', 'step', 'next', 'until',
+ 'jump', 'return', 'retval', 'run', 'continue', 'list', 'longlist',
+ 'args', 'print', 'pp', 'whatis', 'source', 'display', 'undisplay',
+ 'interact', 'alias', 'unalias', 'debug', 'quit',
+ ]
+
+ for _command in _help_order:
+ __doc__ += getattr(SyncPdb, 'do_' + _command).__doc__.strip() + '\n\n'
+ __doc__ += SyncPdb.help_exec.__doc__
+
+ del _help_order, _command
+
+
+ # Simplified interface
+
+ def run(statement, globals=None, locals=None):
+ SyncPdb().run(statement, globals, locals)
+
+ def runeval(expression, globals=None, locals=None):
+ return SyncPdb().runeval(expression, globals, locals)
+
+ def runctx(statement, globals, locals):
+ # B/W compatibility
+ run(statement, globals, locals)
+
+ def runcall(*args, **kwds):
+ return SyncPdb().runcall(*args, **kwds)
+
+ def set_trace():
+ SyncPdb().set_trace(sys._getframe().f_back)
+
+ # Post-Mortem interface
+
+ def post_mortem(t=None):
+ # handling the default
+ if t is None:
+ # sys.exc_info() returns (type, value, traceback) if an exception is
+ # being handled, otherwise it returns None
+ t = sys.exc_info()[2]
+ if t is None:
+ raise ValueError("A valid traceback must be passed if no "
+ "exception is being handled")
+
+ p = SyncPdb()
+ p.reset()
+ p.interaction(None, t)
+
+ def pm():
+ post_mortem(sys.last_traceback)
+
+
+ # Main program for testing
+
+ TESTCMD = 'import x; x.main()'
+
+ def test():
+ run(TESTCMD)
+
+ # print help
+ def help():
+ import pydoc
+ pydoc.pager(__doc__)
+
+ _usage = """\
+ usage: syncpdb.py [-c command] ... pyfile [arg] ...
+
+ Debug the Python program given by pyfile.
+
+ Initial commands are read from .pdbrc files in your home directory
+ and in the current directory, if they exist. Commands supplied with
+ -c are executed after commands from .pdbrc files.
+
+ To let the script run until an exception occurs, use "-c continue".
+ To let the script run up to a given line X in the debugged file, use
+ "-c 'until X'"."""
+
+
+
+
+if sys.version_info == 2:
+ def main():
+ if not sys.argv[1:] or sys.argv[1] in ("--help", "-h"):
+ # SPdb
+ print("usage: syncpdb.py scriptfile [arg] ...")
+ # /SPdb
+ sys.exit(2)
+
+ mainpyfile = sys.argv[1] # Get script filename
+ if not os.path.exists(mainpyfile):
+ # SPdb
+ print('Error:', mainpyfile, 'does not exist')
+ # /SPdb
+ sys.exit(1)
+
+ del sys.argv[0] # Hide "pdb.py" from argument list
+
+ # Replace pdb's dir with script's dir in front of module search path.
+ sys.path[0] = os.path.dirname(mainpyfile)
+
+ # Note on saving/restoring sys.argv: it's a good idea when sys.argv was
+ # modified by the script being debugged. It's a bad idea when it was
+ # changed by the user from the command line. There is a "restart" command
+ # which allows explicit specification of command line arguments.
+ syncpdb = SyncPdb()
+ while True:
+ try:
+ syncpdb._runscript(mainpyfile)
+ if syncpdb._user_requested_quit:
+ break
+ # SPdb
+ print("The program finished and will be restarted")
+ # /SPdb
+ except Restart:
+ # SPdb
+ print("Restarting", mainpyfile, "with arguments:")
+ print("\t" + " ".join(sys.argv[1:]))
+ # /SPdb
+ except SystemExit:
+ # In most cases SystemExit does not warrant a post-mortem session.
+ # SPdb
+ print("The program exited via sys.exit(). Exit status: {0}".format(sys.exc_info()[1]))
+ # /SPdb
+ except:
+ traceback.print_exc()
+ # SPdb
+ print("Uncaught exception. Entering post mortem debugging")
+ print("Running 'cont' or 'step' will restart the program")
+ # /SPdb
+ t = sys.exc_info()[2]
+ syncpdb.interaction(None, t)
+ # SPdb
+ print("Post mortem debugger finished. The {0} will be restarted".format(mainpyfile))
+ # /SPdb
+else:
+ def main():
+ import getopt
+
+ opts, args = getopt.getopt(sys.argv[1:], 'hc:', ['--help', '--command='])
+
+ if not args:
+ print(_usage)
+ sys.exit(2)
+
+ commands = []
+ for opt, optarg in opts:
+ if opt in ['-h', '--help']:
+ print(_usage)
+ sys.exit()
+ elif opt in ['-c', '--command']:
+ commands.append(optarg)
+
+ mainpyfile = args[0] # Get script filename
+ if not os.path.exists(mainpyfile):
+ print('Error:', mainpyfile, 'does not exist')
+ sys.exit(1)
+
+ sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list
+
+ # Replace pdb's dir with script's dir in front of module search path.
+ sys.path[0] = os.path.dirname(mainpyfile)
+
+ # Note on saving/restoring sys.argv: it's a good idea when sys.argv was
+ # modified by the script being debugged. It's a bad idea when it was
+ # changed by the user from the command line. There is a "restart" command
+ # which allows explicit specification of command line arguments.
+ syncpdb = SyncPdb()
+ syncpdb.rcLines.extend(commands)
+ while True:
+ try:
+ syncpdb._runscript(mainpyfile)
+ if syncpdb._user_requested_quit:
+ break
+ print("The program finished and will be restarted")
+ except Restart:
+ print("Restarting", mainpyfile, "with arguments:")
+ print("\t" + " ".join(args))
+ except SystemExit:
+ # In most cases SystemExit does not warrant a post-mortem session.
+ # SPdb
+ print("The program exited via sys.exit(). Exit status: {0}".format(sys.exc_info()[1]))
+ # /SPdb
+ except:
+ traceback.print_exc()
+ print("Uncaught exception. Entering post mortem debugging")
+ print("Running 'cont' or 'step' will restart the program")
+ t = sys.exc_info()[2]
+ syncpdb.interaction(None, t)
+ print("Post mortem debugger finished. The " + mainpyfile +
+ " will be restarted")
+
+
+# When invoked as main program, invoke the debugger on a script
+if __name__ == '__main__':
+ import syncpdb
+ syncpdb.main()
diff --git a/Master/texmf-dist/scripts/pythontex/depythontex.py b/Master/texmf-dist/scripts/pythontex/depythontex.py
index 013e268241f..2aae645084f 100755
--- a/Master/texmf-dist/scripts/pythontex/depythontex.py
+++ b/Master/texmf-dist/scripts/pythontex/depythontex.py
@@ -1,10 +1,15 @@
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
+
'''
-This is the PythonTeX wrapper script. It automatically detects the version
+This is the depythontex wrapper script. It automatically detects the version
of Python, and then imports the correct code from depythontex2.py or
-depythontex3.py.
+depythontex3.py. It is intended for use with the default Python installation
+on your system. If you wish to use a different version of Python, you could
+launch depythontex2.py or depythontex3.py directly. The version of Python
+does not matter for depythontex, since no code is executed.
-Copyright (c) 2013, Geoffrey M. Poore
+Copyright (c) 2013-2014, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
diff --git a/Master/texmf-dist/scripts/pythontex/depythontex2.py b/Master/texmf-dist/scripts/pythontex/depythontex2.py
index 028badcb88e..cc6f30b0a68 100755
--- a/Master/texmf-dist/scripts/pythontex/depythontex2.py
+++ b/Master/texmf-dist/scripts/pythontex/depythontex2.py
@@ -1,4 +1,6 @@
+#!/usr/bin/env python2
# -*- coding: utf-8 -*-
+
'''
PythonTeX depythontex script.
@@ -45,7 +47,7 @@ example, typeset code may have a different appearance or layout when it is
typeset with a different package.
-Copyright (c) 2013, Geoffrey M. Poore
+Copyright (c) 2013-2014, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
@@ -63,6 +65,15 @@ from __future__ import unicode_literals
import sys
import os
#// Python 2
+if sys.version_info.major != 2:
+ sys.exit('This version of the PythonTeX script requires Python 2.')
+#\\ End Python 2
+#// Python 3
+#if sys.version_info.major != 3:
+# sys.exit('This version of the PythonTeX script requires Python 3.')
+#\\ End Python 3
+
+#// Python 2
from io import open
input = raw_input
#\\ End Python 2
@@ -70,13 +81,12 @@ import argparse
from collections import defaultdict
from re import match, sub, search
import textwrap
+import codecs
# Script parameters
# Version
-version = 'v0.12'
-
-
+version = 'v0.13'
# Functions and parameters for customizing the script output
@@ -678,10 +688,10 @@ 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('-o', '--output', default=None,
+ help='output file')
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
@@ -715,8 +725,9 @@ elif args.listing == 'pythontex':
# Let the user know things have started
-print('This is DePythonTeX {0}'.format(version))
-sys.stdout.flush()
+if args.output is not None:
+ print('This is DePythonTeX {0}'.format(version))
+ sys.stdout.flush()
@@ -736,17 +747,14 @@ if not os.path.isfile(texfile_name):
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)
+if args.output is not None:
+ outfile_name = os.path.expanduser(os.path.normcase(args.output))
+ 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):
@@ -792,7 +800,8 @@ if settings['version'] != version:
# 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)
+if args.output is not None:
+ outfile = open(outfile_name, 'w', encoding=encoding)
@@ -809,8 +818,8 @@ if 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)
+if os.path.isfile(os.path.expanduser(os.path.normcase(settings['macrofile']))):
+ f = open(os.path.expanduser(os.path.normcase(settings['macrofile'])), 'r', encoding=encoding)
macros = f.readlines()
f.close()
else:
@@ -986,7 +995,7 @@ for n, depytxline in enumerate(depytx):
f_name, mode = f_name.split(':mode=')
else:
mode = None
- f = open(f_name, 'r', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(f_name)), 'r', encoding=encoding)
replacement = f.read()
f.close()
if typeset == 'c':
@@ -1360,11 +1369,15 @@ for n, line in enumerate(texout):
if startline == n:
if bool(search(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', line)):
texout[n] = sub(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', '', line)
+ if texout[n].isspace():
+ texout[n] = ''
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)
+ if replacement.isspace():
+ replacement = ''
texout[startline] = replacement
for l in range(startline+1, n+1):
texout[l] = ''
@@ -1399,6 +1412,16 @@ if forced_double_space_list:
# Write output
-for line in texout:
- outfile.write(line)
-outfile.close()
+if args.output is not None:
+ for line in texout:
+ outfile.write(line)
+ outfile.close()
+else:
+ 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')
+ for line in texout:
+ sys.stdout.write(line)
diff --git a/Master/texmf-dist/scripts/pythontex/depythontex3.py b/Master/texmf-dist/scripts/pythontex/depythontex3.py
index 996b8447316..eef2f75b8f8 100755
--- a/Master/texmf-dist/scripts/pythontex/depythontex3.py
+++ b/Master/texmf-dist/scripts/pythontex/depythontex3.py
@@ -1,4 +1,6 @@
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+
'''
PythonTeX depythontex script.
@@ -45,7 +47,7 @@ example, typeset code may have a different appearance or layout when it is
typeset with a different package.
-Copyright (c) 2013, Geoffrey M. Poore
+Copyright (c) 2013-2014, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
@@ -63,6 +65,15 @@ Licensed under the BSD 3-Clause License:
import sys
import os
#// Python 2
+#if sys.version_info.major != 2:
+# sys.exit('This version of the PythonTeX script requires Python 2.')
+#\\ End Python 2
+#// Python 3
+if sys.version_info.major != 3:
+ sys.exit('This version of the PythonTeX script requires Python 3.')
+#\\ End Python 3
+
+#// Python 2
#from io import open
#input = raw_input
#\\ End Python 2
@@ -70,13 +81,12 @@ import argparse
from collections import defaultdict
from re import match, sub, search
import textwrap
+import codecs
# Script parameters
# Version
-version = 'v0.12'
-
-
+version = 'v0.13'
# Functions and parameters for customizing the script output
@@ -678,10 +688,10 @@ 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('-o', '--output', default=None,
+ help='output file')
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
@@ -715,8 +725,9 @@ elif args.listing == 'pythontex':
# Let the user know things have started
-print('This is DePythonTeX {0}'.format(version))
-sys.stdout.flush()
+if args.output is not None:
+ print('This is DePythonTeX {0}'.format(version))
+ sys.stdout.flush()
@@ -736,17 +747,14 @@ if not os.path.isfile(texfile_name):
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)
+if args.output is not None:
+ outfile_name = os.path.expanduser(os.path.normcase(args.output))
+ 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):
@@ -792,7 +800,8 @@ if settings['version'] != version:
# 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)
+if args.output is not None:
+ outfile = open(outfile_name, 'w', encoding=encoding)
@@ -809,8 +818,8 @@ if 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)
+if os.path.isfile(os.path.expanduser(os.path.normcase(settings['macrofile']))):
+ f = open(os.path.expanduser(os.path.normcase(settings['macrofile'])), 'r', encoding=encoding)
macros = f.readlines()
f.close()
else:
@@ -986,7 +995,7 @@ for n, depytxline in enumerate(depytx):
f_name, mode = f_name.split(':mode=')
else:
mode = None
- f = open(f_name, 'r', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(f_name)), 'r', encoding=encoding)
replacement = f.read()
f.close()
if typeset == 'c':
@@ -1360,11 +1369,15 @@ for n, line in enumerate(texout):
if startline == n:
if bool(search(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', line)):
texout[n] = sub(r'\\usepackage(?:\[.*?\]){0,1}\{pythontex\}', '', line)
+ if texout[n].isspace():
+ texout[n] = ''
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)
+ if replacement.isspace():
+ replacement = ''
texout[startline] = replacement
for l in range(startline+1, n+1):
texout[l] = ''
@@ -1399,6 +1412,16 @@ if forced_double_space_list:
# Write output
-for line in texout:
- outfile.write(line)
-outfile.close()
+if args.output is not None:
+ for line in texout:
+ outfile.write(line)
+ outfile.close()
+else:
+ 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')
+ for line in texout:
+ sys.stdout.write(line)
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex.py b/Master/texmf-dist/scripts/pythontex/pythontex.py
index 1be1f6d70ea..68814f54884 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex.py
@@ -4,11 +4,19 @@
'''
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.
+pythontex3.py. It is intended for use with the default Python installation
+on your system. If you wish to use a different version of Python, you could
+launch pythontex2.py or pythontex3.py directly. You should also consider the
+command-line option `--interpreter`. This allows you to specify the command
+that is actually used to execute the code from your LaTeX documents. Except
+for Python console content, it doesn't matter which version of Python is used
+to launch pythontex.py; pythontex.py just manages the execution of code from
+your LaTeX document. The interpreter setting is what determines the version
+under which your code is actually executed.
Licensed under the BSD 3-Clause License:
-Copyright (c) 2012-2013, Geoffrey M. Poore
+Copyright (c) 2012-2014, Geoffrey M. Poore
All rights reserved.
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex2.py b/Master/texmf-dist/scripts/pythontex/pythontex2.py
index c123adee22b..d266616ce90 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex2.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex2.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python2
# -*- coding: utf-8 -*-
'''
@@ -13,7 +13,7 @@ should be in the same directory.
Licensed under the BSD 3-Clause License:
-Copyright (c) 2012-2013, Geoffrey M. Poore
+Copyright (c) 2012-2014, Geoffrey M. Poore
All rights reserved.
@@ -61,6 +61,7 @@ import multiprocessing
from pygments.styles import get_all_styles
from pythontex_engines import *
import textwrap
+import platform
if sys.version_info[0] == 2:
try:
@@ -76,7 +77,7 @@ else:
# Script parameters
# Version
-version = 'v0.12'
+version = 'v0.13'
@@ -84,35 +85,36 @@ 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.family, self.session, self.restart, self.instance, self.command, self.context, self.args_run, self.args_prettyprint, self.input_file, self.line = self.delims.split('#')
+ self.instance_int = int(self.instance)
+ self.line_int = int(self.line)
+ self.key_run = self.family + '#' + self.session + '#' + self.restart
+ self.key_typeset = self.key_run + '#' + self.instance
+ self.hashable_delims_run = self.key_typeset + '#' + self.command + '#' + self.context + '#' + self.args_run
+ self.hashable_delims_typeset = self.key_typeset + '#' + self.command + '#' + self.context + '#' + self.args_run
+ if len(self.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)
+ self.line_int += 1
+ self.line = str(self.line_int)
else:
self.is_inline = True
- self.is_extfile = True if self.input_session.startswith('EXT:') else False
+ self.is_extfile = True if self.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
+ self.extfile = os.path.expanduser(os.path.normcase(self.session.replace('EXT:', '', 1)))
+ self.key_typeset = self.key_typeset.replace('EXT:', '')
+ self.is_cc = True if self.family.startswith('CC:') else False
+ self.is_pyg = True if self.family.startswith('PYG') else False
+ self.is_verb = True if self.restart.endswith('verb') else False
if self.is_cc:
- self.input_instance += 'CC'
- self.cc_type, self.cc_pos = self.input_family.split(':')[1:]
+ self.instance += 'CC'
+ self.cc_type, self.cc_pos = self.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_cons = engine_dict[self.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):
+ if self.command in ('c', 'code') or (self.command == 'i' and not self.is_cons):
self.is_typeset = False
else:
self.is_typeset = True
@@ -142,19 +144,30 @@ def process_argv(data, temp_data):
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')
+ group_run = parser.add_mutually_exclusive_group()
+ group_run.add_argument('--runall', nargs='?', default='false',
+ const='true', choices=('true', 'false'),
+ help='run ALL code; equivalent to package option')
+ group_run.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('-j', '--jobs', metavar='N', default=None, type=int,
+ help='Allow N jobs at once; defaults to cpu_count().')
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')
+ group_debug = parser.add_mutually_exclusive_group()
+ group_debug.add_argument('--debug', nargs='?', default=None,
+ const='default',
+ metavar='<family>:<session>:<restart>',
+ help='Run the specified session (or default session) with the default debugger, if available. If there is only one session, it need not be specified. If the session name is unambiguous, it is sufficient. The full <family>:<session>:<restart> (for example, py:default:default) is only needed when the session name alone would be ambiguous.')
+ group_debug.add_argument('--interactive', nargs='?', default=None,
+ const='default',
+ metavar='<family>:<session>:<restart>',
+ help='Run the specified session (or default session) in interactive mode. If there is only one session, it need not be specified. If the session name is unambiguous, it is sufficient. The full <family>:<session>:<restart> (for example, py:default:default) is only needed when the session name alone would be ambiguous.')
args = parser.parse_args()
# Store the parsed argv in data and temp_data
@@ -174,8 +187,19 @@ def process_argv(data, temp_data):
temp_data['hashdependencies'] = True
else:
temp_data['hashdependencies'] = False
+ if args.jobs is None:
+ try:
+ jobs = multiprocessing.cpu_count()
+ except NotImplementedError:
+ jobs = 1
+ temp_data['jobs'] = jobs
+ else:
+ temp_data['jobs'] = args.jobs
temp_data['verbose'] = args.verbose
+ temp_data['debug'] = args.debug
+ temp_data['interactive'] = args.interactive
# Update interpreter_dict based on interpreter
+ set_python_interpreter = False
if args.interpreter is not None:
interp_list = args.interpreter.lstrip('{').rstrip('}').split(',')
for interp in interp_list:
@@ -185,10 +209,65 @@ def process_argv(data, temp_data):
k = k.strip(' \'"')
v = v.strip(' \'"')
interpreter_dict[k] = v
+ if k == 'python':
+ set_python_interpreter = True
except:
print('Invalid --interpreter argument')
return sys.exit(2)
-
+ # If the Python interpreter wasn't set, then try to set an appropriate
+ # default value, based on how PythonTeX was launched (pythontex.py,
+ # pythontex2.py, or pythontex3.py).
+ if not set_python_interpreter:
+ if temp_data['python'] == 2:
+ if platform.system() == 'Windows':
+ try:
+ subprocess.check_output(['py', '--version'])
+ interpreter_dict['python'] = 'py -2'
+ except:
+ msg = '''
+ * PythonTeX error:
+ You have launched PythonTeX using pythontex{0}.py
+ directly. This should only be done when you want
+ to use Python version {0}, but have a different
+ version installed as the default. (Otherwise, you
+ should start PythonTeX with pythontex.py.) For
+ this to work correctly, you should install Python
+ version 3.3+, which has a Windows wrapper (py) that
+ PythonTeX can use to run the correct version of
+ Python. If you do not want to install Python 3.3+,
+ you can also use the --interpreter command-line
+ option to tell PythonTeX how to access the version
+ of Python you wish to use.
+ '''.format(temp_data['python'])
+ print(textwrap.dedent(msg[1:]))
+ return sys.exit(2)
+ else:
+ interpreter_dict['python'] = 'python2'
+ elif temp_data['python'] == 3:
+ if platform.system() == 'Windows':
+ try:
+ subprocess.check_output(['py', '--version'])
+ interpreter_dict['python'] = 'py -3'
+ except:
+ msg = '''
+ * PythonTeX error:
+ You have launched PythonTeX using pythontex{0}.py
+ directly. This should only be done when you want
+ to use Python version {0}, but have a different
+ version installed as the default. (Otherwise, you
+ should start PythonTeX with pythontex.py.) For
+ this to work correctly, you should install Python
+ version 3.3+, which has a Windows wrapper (py) that
+ PythonTeX can use to run the correct version of
+ Python. If you do not want to install Python 3.3+,
+ you can also use the --interpreter command-line
+ option to tell PythonTeX how to access the version
+ of Python you wish to use.
+ '''.format(temp_data['python'])
+ print(textwrap.dedent(msg[1:]))
+ return sys.exit(2)
+ else:
+ interpreter_dict['python'] = 'python3'
if args.TEXNAME is not None:
# Determine if we a dealing with just a filename, or a name plus
@@ -332,7 +411,7 @@ def load_code_get_settings(data, temp_data):
else:
settings[k] = v
def set_kv_pygments(k, v):
- input_family, lexer_opts, options = v.replace(' ','').split('|')
+ family, lexer_opts, options = v.replace(' ','').split('|')
lexer = None
lex_dict = {}
opt_dict = {}
@@ -358,7 +437,7 @@ def load_code_get_settings(data, temp_data):
k = option
v = True
opt_dict[k] = v
- if input_family != ':GLOBAL':
+ if family != ':GLOBAL':
if 'lexer' in pygments_settings[':GLOBAL']:
lexer = pygments_settings[':GLOBAL']['lexer']
lex_dict.update(pygments_settings[':GLOBAL']['lexer_options'])
@@ -367,9 +446,9 @@ def load_code_get_settings(data, temp_data):
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
+ pygments_settings[family]['lexer'] = lexer
+ pygments_settings[family]['lexer_options'] = lex_dict
+ pygments_settings[family]['formatter_options'] = opt_dict
settings_func['version'] = set_kv_data
settings_func['outputdir'] = set_kv_data
settings_func['workingdir'] = set_kv_data
@@ -468,7 +547,7 @@ def get_old_data(data, old_data, temp_data):
'''
# Create a string containing the name of the data file
- pythontex_data_file = os.path.join(data['settings']['outputdir'], 'pythontex_data.pkl')
+ pythontex_data_file = os.path.expanduser(os.path.normcase(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):
@@ -498,14 +577,15 @@ def get_old_data(data, old_data, temp_data):
temp_data['loaded_old_data'] = False
# Set the utilspath
+ # Assume that if the utils aren't in the same location as
+ # `pythontex.py`, then they are somewhere else on `sys.path` that
+ # will always be available (for example, installed as a Python module),
+ # and thus specifying a path isn't necessary.
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)
+ data['utilspath'] = ''
@@ -525,7 +605,7 @@ def modified_dependencies(key, data, old_data, temp_data):
# 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)
+ dep_file = os.path.expanduser(os.path.normcase(os.path.join(workingdir, dep_file)))
if not os.path.isfile(dep_file):
print('* PythonTeX error')
print(' Cannot find dependency "' + dep + '"')
@@ -544,9 +624,9 @@ def modified_dependencies(key, data, old_data, temp_data):
# would require an unnecessary decoding and encoding cycle.
f = open(dep_file, 'rb')
hasher = sha1()
- hash = hasher(f.read()).hexdigest()
+ h = hasher(f.read()).hexdigest()
f.close()
- if hash != old_dep_hash_dict[dep][1]:
+ if h != old_dep_hash_dict[dep][1]:
return True
else:
mtime = os.path.getmtime(dep_file)
@@ -625,6 +705,7 @@ def hash_all(data, temp_data, old_data, engine_dict):
if c.is_typeset:
typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding))
typeset_hasher[c.key_typeset].update(code_encoded)
+ typeset_hasher[c.key_typeset].update(c.args_prettyprint.encode(encoding))
elif c.is_cons:
cons_hasher[c.key_run].update(c.hashable_delims_run.encode(encoding))
code_encoded = c.code.encode(encoding)
@@ -632,29 +713,31 @@ def hash_all(data, temp_data, old_data, engine_dict):
if c.is_typeset:
typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding))
typeset_hasher[c.key_typeset].update(code_encoded)
+ typeset_hasher[c.key_typeset].update(c.args_prettyprint.encode(encoding))
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))
+ typeset_hasher[c.key_typeset].update(c.args_prettyprint.encode(encoding))
# Store hashes
code_hash_dict = {}
for key in code_hasher:
- input_family = key.split('#', 1)[0]
+ family = key.split('#', 1)[0]
code_hash_dict[key] = (code_hasher[key].hexdigest(),
- cc_hasher[input_family].hexdigest(),
- engine_dict[input_family].get_hash())
+ cc_hasher[family].hexdigest(),
+ engine_dict[family].get_hash())
data['code_hash_dict'] = code_hash_dict
cons_hash_dict = {}
for key in cons_hasher:
- input_family = key.split('#', 1)[0]
+ family = key.split('#', 1)[0]
cons_hash_dict[key] = (cons_hasher[key].hexdigest(),
- cc_hasher[input_family].hexdigest(),
- engine_dict[input_family].get_hash())
+ cc_hasher[family].hexdigest(),
+ engine_dict[family].get_hash())
data['cons_hash_dict'] = cons_hash_dict
typeset_hash_dict = {}
@@ -741,9 +824,9 @@ def hash_all(data, temp_data, old_data, engine_dict):
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
+ family = key.split('#', 1)[0]
+ if family in pygments_settings:
+ if (not pygments_settings_changed[family] and
key in old_typeset_hash_dict and
typeset_hash_dict[key] == old_typeset_hash_dict[key]):
pygments_update[key] = False
@@ -768,8 +851,8 @@ def hash_all(data, temp_data, old_data, engine_dict):
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:
+ family = key.split('#', 1)[0]
+ if family in pygments_settings:
pygments_update[key] = True
else:
pygments_update[key] = False
@@ -866,6 +949,57 @@ def parse_code_write_scripts(data, temp_data, engine_dict):
cons_update = temp_data['cons_update']
pygments_update = temp_data['pygments_update']
files = data['files']
+ debug = temp_data['debug']
+ interactive = temp_data['interactive']
+
+ # Tweak the update dicts to work with debug command-line option.
+ # #### This should probably be refactored later, once the debug interface
+ # stabilizes
+ if debug is not None or interactive is not None:
+ if debug is not None:
+ arg = debug
+ else:
+ arg = interactive
+ for k in cons_update:
+ cons_update[k] = False
+ if ':' in arg:
+ # May need to refine in light of substitution of `:` -> `_`
+ # in session names?
+ arg_key = arg.replace(':', '#')
+ if arg_key not in code_update:
+ return sys.exit('Session {0} does not exist'.format(arg))
+ else:
+ for k in code_update:
+ code_update[k] = False
+ code_update[arg_key] = True
+ if debug is not None:
+ temp_data['debug_key'] = arg_key
+ else:
+ temp_data['interactive_key'] = arg_key
+ else:
+ session_count_dict = defaultdict(list)
+ for k in code_update:
+ s = k.split('#')[1]
+ session_count_dict[s].append(k)
+ if arg not in session_count_dict:
+ if arg in cons_update:
+ return sys.exit('Console sessions are not currently supported for interactive mode.')
+ else:
+ return sys.exit('Session "{0}" does not exist.'.format(arg))
+ elif len(session_count_dict[arg]) > 1:
+ return sys.exit('Ambiguous session name "{0}"; please specify <family>:<session>:<restart>'.format(arg))
+ else:
+ for k in code_update:
+ code_update[k] = False
+ arg_key = session_count_dict[arg][0]
+ code_update[arg_key] = True
+ if debug is not None:
+ temp_data['debug_key'] = arg_key
+ else:
+ temp_data['interactive_key'] = arg_key
+
+
+
# 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
@@ -874,8 +1008,8 @@ def parse_code_write_scripts(data, temp_data, engine_dict):
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.instance_int > last_instance[c.key_run]:
+ last_instance[c.key_run] = c.instance_int
if c.is_code:
if code_update[c.key_run]:
code_dict[c.key_run].append(c)
@@ -908,21 +1042,62 @@ def parse_code_write_scripts(data, temp_data, engine_dict):
# 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)
+ family, session, restart = key.split('#')
+ fname = os.path.join(outputdir, family + '_' + session + '_' + restart + '.' + engine_dict[family].extension)
+ # Want to keep track of files without expanding user, but need to
+ # expand user when actually writing files
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])
+ sessionfile = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
+ script, code_index = engine_dict[family].get_script(encoding,
+ utilspath,
+ outputdir,
+ workingdir,
+ cc_dict_begin[family],
+ code_dict[key],
+ cc_dict_end[family],
+ debug,
+ interactive)
for lines in script:
sessionfile.write(lines)
sessionfile.close()
code_index_dict[key] = code_index
temp_data['code_index_dict'] = code_index_dict
+
+ # Write synchronization file if in debug mode
+ if debug is not None:
+ # Might improve tracking/cleanup of syncdb files
+ key = temp_data['debug_key']
+ family, session, restart = key.split('#')
+ basename = key.replace('#', '_')
+ syncdb_fname = os.path.join(outputdir, basename + '.' + engine_dict[family].extension + '.syncdb')
+ files[key].append(syncdb_fname)
+ # #### In future version, try to use currfile to get this information
+ # automatically via the .pytxcode
+ main_doc_fname = None
+ for ext in ('.tex', '.ltx', '.dtx'):
+ if os.path.isfile(data['raw_jobname'] + ext):
+ main_doc_fname = data['raw_jobname'] + ext
+ break
+ if not main_doc_fname:
+ return sys.exit('Could not determine extension for main file "{0}"'.format(data['raw_jobname']))
+ main_code_fname = basename + '.' + engine_dict[family].extension
+ f = open(os.path.expanduser(os.path.normcase(syncdb_fname)), 'w', encoding='utf8')
+ f.write('{0},,{1},,\n'.format(main_code_fname, main_doc_fname))
+ # All paths are relative to the main code file. So if there is ever
+ # an option for creating other code files, in other locations, then
+ # the relative paths to those files will need to be specified.
+ for e in code_index_dict[key].values():
+ # #### Probably redo approach so this conversion isn't needed
+ if not e.input_file:
+ input_file = main_doc_fname
+ else:
+ input_file = e.input_file
+ if ',' in input_file or ',' in main_code_fname:
+ line = '"{0}",{1},"{2}",{3},{4}\n'.format(main_code_fname, e.lines_total+1, input_file, e.line_int, e.lines_input)
+ else:
+ line = '{0},{1},{2},{3},{4}\n'.format(main_code_fname, e.lines_total+1, input_file, e.line_int, e.lines_input)
+ f.write(line)
+ f.close()
@@ -935,6 +1110,7 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
keeptemps = data['settings']['keeptemps']
fvextfile = data['settings']['fvextfile']
pygments_settings = data['pygments_settings']
+ jobs = temp_data['jobs']
verbose = temp_data['verbose']
code_dict = temp_data['code_dict']
@@ -961,87 +1137,165 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
dependencies = data['dependencies']
exit_status = data['exit_status']
start_time = data['start_time']
+ debug = temp_data['debug']
+ interactive = temp_data['interactive']
+
+ # If in debug or interactive mode, short-circuit the whole process
+ # #### This should probably be refactored later, once debugging is more
+ # mature
+ if debug is not None or interactive is not None:
+ import shlex
+ if debug is not None:
+ print('Entering debug mode for "{0}"\n'.format(debug) + '-'*20 + '\n')
+ key = temp_data['debug_key']
+ else:
+ print('Entering interactive mode for "{0}"\n'.format(interactive) + '-'*20 + '\n')
+ key = temp_data['interactive_key']
+ basename = key.replace('#', '_')
+ family, session, restart = key.split('#')
+ # #### Revise as debugging is expanded
+ if debug is not None and engine_dict[family].language != 'python':
+ return sys.exit('Currently, debug only supports Python')
+ if debug is not None:
+ # #### Eventually, should move to pythontex_engines.py and
+ # provide means for customization
+ command = '{python} {debug} {file}.py --interactive'
+ command = command.replace('{python}', interpreter_dict['python'])
+ command = command.replace('{debug}', '"{0}"'.format(os.path.join(sys.path[0], 'syncpdb.py')))
+ else:
+ command = engine_dict[family].command + ' --interactive'
+ # Need to be in script directory so that pdb and any other tools that
+ # expect this will function correctly.
+ orig_cwd = os.getcwd()
+ if outputdir:
+ os.chdir(os.path.expanduser(os.path.normcase(outputdir)))
+ # Note that command is a string, which must be converted to list
+ # Must double-escape any backslashes so that they survive `shlex.split()`
+ script = basename
+ if os.path.isabs(os.path.expanduser(os.path.normcase(outputdir))):
+ script_full = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename)))
+ else:
+ script_full = os.path.expanduser(os.path.normcase(os.path.join(orig_cwd, 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('\\', '\\\\'), File=script_full.replace('\\', '\\\\'))))
+ exec_cmd = [unicode(elem) for elem in exec_cmd]
+ else:
+ exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\'), File=script_full.replace('\\', '\\\\')))
+ try:
+ proc = subprocess.Popen(exec_cmd)
+ 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)
+ else:
+ raise
+ proc.wait()
+ os.chdir(orig_cwd)
+ # Do a basic update of pickled data
+ # This is only really needed for tracking the code file and the
+ # synchronization file (if it was created)
+ if temp_data['loaded_old_data'] and key in old_data['exit_status']:
+ exit_status[key] = old_data['exit_status'][key]
+ else:
+ exit_status[key] = (None, None)
+ if temp_data['loaded_old_data']:
+ data['last_new_file_time'] = old_data['last_new_file_time']
+ else:
+ data['last_new_file_time'] = start_time
+ pythontex_data_file = os.path.expanduser(os.path.normcase(os.path.join(outputdir, 'pythontex_data.pkl')))
+ f = open(pythontex_data_file, 'wb')
+ pickle.dump(data, f, -1)
+ f.close()
+ return
- # 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)
+ # Create a pool for multiprocessing. Set the maximum number of
+ # concurrent processes to a user-specified value for jobs. If the user
+ # has not specified a value, then it will be None, and
+ # multiprocessing.Pool() will use cpu_count().
+ pool = multiprocessing.Pool(jobs)
tasks = []
# If verbose, print a list of processes
if verbose:
- print('\n* PythonTeX will run the following processes:')
+ print('\n* PythonTeX will run the following processes')
+ print(' with working directory {0}'.format(workingdir))
+ print(' (maximum concurrent processes = {0})'.format(jobs))
# 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]
+ 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,
+ engine_dict[family].language,
+ engine_dict[family].command,
+ engine_dict[family].created,
+ engine_dict[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,
+ engine_dict[family].errors,
+ engine_dict[family].warnings,
+ engine_dict[family].linenumbers,
+ engine_dict[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,
+ engine_dict[family].language,
+ engine_dict[family].command,
+ engine_dict[family].created,
+ engine_dict[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,
+ engine_dict[family].errors,
+ engine_dict[family].warnings,
+ engine_dict[family].linenumbers,
+ engine_dict[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:
+ family = key.split('#')[0]
+ if engine_dict[family].language.startswith('python'):
+ if 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)'''
+ fvextfile, pygments_settings[family],
+ cc_dict_begin[family], cons_dict[key],
+ cc_dict_end[family], engine_dict[family].startup,
+ engine_dict[family].banner,
+ engine_dict[family].filename)'''
tasks.append(pool.apply_async(python_console, [jobname, encoding,
outputdir, workingdir,
fvextfile,
- pygments_settings[input_family],
- cc_dict_begin[input_family],
+ pygments_settings[family],
+ cc_dict_begin[family],
cons_dict[key],
- cc_dict_end[input_family],
- engine_dict[input_family].startup,
- engine_dict[input_family].banner,
- engine_dict[input_family].filename]))
+ cc_dict_end[family],
+ engine_dict[family].startup,
+ engine_dict[family].banner,
+ engine_dict[family].filename]))
else:
tasks.append(pool.apply_async(python_console, [jobname, encoding,
outputdir, workingdir,
fvextfile,
None,
- cc_dict_begin[input_family],
+ cc_dict_begin[family],
cons_dict[key],
- cc_dict_end[input_family],
- engine_dict[input_family].startup,
- engine_dict[input_family].banner,
- engine_dict[input_family].filename]))
+ cc_dict_end[family],
+ engine_dict[family].startup,
+ engine_dict[family].banner,
+ engine_dict[family].filename]))
else:
print('* PythonTeX error')
print(' Currently, non-Python consoles are not supported')
@@ -1113,7 +1367,7 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
# beginning of the run. If so, reset them so they will run next time and
# issue a warning
unresolved_dependencies = False
- unresolved_sessions= []
+ unresolved_sessions = []
for key in dependencies:
for dep, val in dependencies[key].items():
if val[0] > start_time:
@@ -1140,13 +1394,13 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
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 = open(os.path.expanduser(os.path.normcase(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)
+ pygments_macro_file = open(os.path.expanduser(os.path.normcase(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:
@@ -1156,7 +1410,7 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
pygments_macro_file.write(''.join(pygments_macros[key]))
pygments_macro_file.close()
- pythontex_data_file = os.path.join(outputdir, 'pythontex_data.pkl')
+ pythontex_data_file = os.path.expanduser(os.path.normcase(os.path.join(outputdir, 'pythontex_data.pkl')))
f = open(pythontex_data_file, 'wb')
pickle.dump(data, f, -1)
f.close()
@@ -1183,8 +1437,8 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
import shlex
# Create what's needed for storing results
- input_family = code_list[0].input_family
- input_session = code_list[0].input_session
+ family = code_list[0].family
+ session = code_list[0].session
key_run = code_list[0].key_run
files = []
macros = []
@@ -1207,19 +1461,23 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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_name = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename + '.out')))
+ err_file_name = os.path.expanduser(os.path.normcase(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)
+ script = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename)))
+ if os.path.isabs(script):
+ script_full = script
+ else:
+ script_full = os.path.expanduser(os.path.normcase(os.path.join(os.getcwd(), 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 = shlex.split(bytes(command.format(file=script.replace('\\', '\\\\'), File=script_full.replace('\\', '\\\\'))))
exec_cmd = [unicode(elem) for elem in exec_cmd]
else:
- exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\')))
+ exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\'), File=script_full.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:
@@ -1271,7 +1529,10 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if valid_stdout:
# Add created files to created list
for c in created.splitlines():
- files.append(c)
+ if os.path.isabs(os.path.expanduser(os.path.normcase(c))):
+ files.append(c)
+ else:
+ files.append(os.path.join(workingdir, c))
# Create a set of dependencies, to eliminate duplicates in the event
# that there are any. This is mainly useful when dependencies are
@@ -1283,7 +1544,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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)
+ dep_file = os.path.expanduser(os.path.normcase(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
@@ -1313,21 +1574,21 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if block:
delims, content = block.split('#\n', 1)
if content:
- input_instance, input_command = delims.split('#')
- if input_instance.endswith('CC'):
+ instance, command = delims.split('#')
+ if instance.endswith('CC'):
messages.append('* PythonTeX warning')
- messages.append(' Custom code for "' + input_family + '" attempted to print or write to stdout')
+ messages.append(' Custom code for "' + 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'
+ elif command == 'i':
+ content = r'\pytx@SVMCR{pytx@MCR@' + key_run.replace('#', '@') + '@' + 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)
+ fname = os.path.join(outputdir, basename + '_' + instance + '.stdout')
+ f = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
f.write(content)
f.close()
files.append(fname)
@@ -1361,7 +1622,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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)
+ # #### Consider os.path.normcase(), making search case-insensitive
+ outputdir_exp = os.path.expanduser(outputdir)
+ fullbasename_correct = os.path.join(outputdir_exp, basename)
if '\\' in fullbasename_correct:
fullbasename_reslashed = fullbasename_correct.replace('\\', '/')
else:
@@ -1401,9 +1664,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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)
+ doclinenum = str(index_now[1].line_int + index_now[1].lines_input)
else:
- doclinenum = str(index_now[1].input_line_int + errlinenum - index_now[1].lines_total - 1)
+ doclinenum = str(index_now[1].line_int + errlinenum - index_now[1].lines_total - 1)
input_file = index_now[1].input_file
else:
doclinenum = '??'
@@ -1469,7 +1732,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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'))
+ err_messages_ud.append(' ' + line.replace(outputdir_exp, '<outputdir>').rstrip('\n'))
else:
err_messages_ud.append(' ' + line.rstrip('\n'))
@@ -1513,7 +1776,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
process = False
else:
process = True
- if len(index_now[1].input_command) > 1:
+ if len(index_now[1].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:
@@ -1540,7 +1803,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if stderrfilename == 'full':
line = line.replace(fullbasename, basename)
elif stderrfilename == 'session':
- line = line.replace(fullbasename, input_session)
+ line = line.replace(fullbasename, session)
elif stderrfilename == 'genericfile':
line = line.replace(fullbasename + '.' + extension, '<file>')
elif stderrfilename == 'genericscript':
@@ -1567,9 +1830,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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
+ instance = last_delim.split('#')[1]
+ doclinenum = str(code_index[instance].line_int)
+ input_file = code_index[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
@@ -1630,13 +1893,13 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
pass
if found:
# Get info from last delim
- input_instance, input_command = last_delim.split('#')[1:-1]
+ instance, command = last_delim.split('#')[1:-1]
# Calculate the line number in the document
- ei = code_index[input_instance]
+ ei = code_index[instance]
if errlinenum > ei.lines_total + ei.lines_input:
- doclinenum = str(ei.input_line_int + ei.lines_input)
+ doclinenum = str(ei.line_int + ei.lines_input)
else:
- doclinenum = str(ei.input_line_int + errlinenum - ei.lines_total - 1)
+ doclinenum = str(ei.line_int + errlinenum - ei.lines_total - 1)
input_file = ei.input_file
else:
doclinenum = '??'
@@ -1704,9 +1967,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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')
+ line = line.replace(outputdir_exp, '<outputdir>').rstrip('\n')
if '/<outputdir>' in line or '\\<outputdir>' in line:
- line = sub(r'(?:(?:[A-Z]:\\)|(?:~?/)).*<outputdir>', '<outputdir>', line)
+ line = sub(r'(?:(?:[A-Za-z]:\\)|(?:~?/)).*<outputdir>', '<outputdir>', line)
msg.append(' ' + line)
else:
msg.append(' ' + line.rstrip('\n'))
@@ -1715,9 +1978,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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
+ instance = last_delim.split('#')[1]
+ doclinenum = str(code_index[instance].line_int)
+ input_file = code_index[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
@@ -1755,12 +2018,12 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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'):
+ instance, command = line.split('#')[1:-1]
+ if instance.endswith('CC'):
process = False
else:
process = True
- err_key = basename + '_' + input_instance
+ err_key = basename + '_' + instance
elif process and basename in line:
found = False
for pattern in linesig:
@@ -1773,14 +2036,14 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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
+ ei = code_index[instance]
+ # Store the `instance` in case it's
# incremented later
- last_input_instance = input_instance
+ last_instance = 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
+ # determine the correct 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
@@ -1790,25 +2053,25 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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]
+ next_instance = str(int(instance) + 1)
+ if next_instance in code_index:
+ next_ei = code_index[next_instance]
if errlinenum > next_ei.lines_total:
- input_instance = next_input_instance
+ instance = next_instance
ei = next_ei
else:
break
else:
break
- if len(input_command) > 1:
+ if len(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
+ # Reset `instance`, in case incremented
+ instance = last_instance
else:
codelinenum = '??'
messages.append('* PythonTeX notice')
@@ -1822,7 +2085,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if stderrfilename == 'full':
line = line.replace(fullbasename, basename)
elif stderrfilename == 'session':
- line = line.replace(fullbasename, input_session)
+ line = line.replace(fullbasename, session)
elif stderrfilename == 'genericfile':
line = line.replace(fullbasename + '.' + extension, '<file>')
elif stderrfilename == 'genericscript':
@@ -1833,7 +2096,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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 = open(os.path.expanduser(os.path.normcase(stderr_file_name)), 'w', encoding=encoding)
f.write(''.join(err_dict[err_key]))
f.close()
files.append(stderr_file_name)
@@ -1841,12 +2104,12 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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)
+ fname = os.path.expanduser(os.path.normcase(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)
+ fname = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename + '.' + ext)))
if os.path.isfile(fname):
os.remove(fname)
files.append(os.path.join(outputdir, basename + '.' + extension))
@@ -1873,7 +2136,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
unknowns_message = '''
* PythonTeX notice
{0} message(s) could not be classified
- Based on the return code, they were interpreted as {1}'''
+ Interpreted as {1}, based on the return code(s)'''
messages[0] += textwrap.dedent(unknowns_message.format(unknowns, unknowns_type))
# Take care of anything that has escaped detection thus far.
@@ -1940,7 +2203,7 @@ def do_pygments(encoding, outputdir, fvextfile, pygments_list,
# Actually parse and highlight the code.
for c in pygments_list:
if c.is_cons:
- content = typeset_cache[c.key_run][c.input_instance]
+ content = typeset_cache[c.key_run][c.instance]
elif c.is_extfile:
if os.path.isfile(c.extfile):
f = open(c.extfile, encoding=encoding)
@@ -1953,16 +2216,27 @@ def do_pygments(encoding, outputdir, fvextfile, pygments_list,
messages.append(' The file was not pygmentized')
else:
content = c.code
- processed = highlight(content, lexer[c.input_family], formatter[c.input_family])
+ processed = highlight(content, lexer[c.family], formatter[c.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'
+ if c.args_prettyprint:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@SaveVerbatim}}[\1, {4}]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance, c.args_prettyprint), processed, count=1)
+ else:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@SaveVerbatim}}[\1]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance), processed, count=1)
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@SaveVerbatim}\n\n'
pygments_macros[c.key_typeset].append(processed)
else:
+ if c.args_prettyprint:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@Verbatim}}[\1, {4}]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance, c.args_prettyprint), processed, count=1)
+ else:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@Verbatim}}[\1]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance), processed, count=1)
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@Verbatim}\n\n'
fname = os.path.join(outputdir, c.key_typeset.replace('#', '_') + '.pygtex')
- f = open(fname, 'w', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
f.write(processed)
f.close()
pygments_files[c.key_typeset].append(fname)
@@ -2052,19 +2326,19 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
if os.getcwd() not in sys.path:
sys.path.append(os.getcwd())
else:
- sys.exit('Cannot find directory {workingdir}')
+ 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:]
+ cons_config = cons_config.format(workingdir=os.path.expanduser(os.path.normcase(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.append('=>PYTHONTEX#{0}#{1}#\n'.format(c.instance, c.command))
self.console_code.extend(c.code.splitlines())
old_stdout = sys.stdout
sys.stdout = self.iostdout
@@ -2104,11 +2378,15 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
# isn't typeset
cons_index = {}
for c in cons_list:
- cons_index[c.input_instance] = c.input_line
+ cons_index[c.instance] = c.line
# Consolize the code
+ # If the working directory is changed as part of the console code,
+ # then we need to get back to where we were.
con = Console(banner, filename)
+ cwd = os.getcwd()
con.consolize(startup, cons_list)
+ os.chdir(cwd)
# Set up Pygments, if applicable
if pygments_settings is not None:
@@ -2134,8 +2412,8 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
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':
+ instance, command = delims.split('#')
+ if instance == 'STARTUP':
exception = False
console_content_lines = console_content.splitlines()
for line in console_content_lines:
@@ -2157,14 +2435,13 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
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'):
+ elif 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:
@@ -2177,15 +2454,15 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
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]))
+ if instance.endswith('CC'):
+ messages.append('* PythonTeX stderr - {0} near line {1} in custom code for console:'.format(alert_type, cons_index[instance]))
else:
- messages.append('* PythonTeX stderr - {0} near line {1} in console code:'.format(alert_type, cons_index[input_instance]))
+ messages.append('* PythonTeX stderr - {0} near line {1} in console code:'.format(alert_type, cons_index[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':
+ if 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.
@@ -2193,38 +2470,45 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
# 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':
+ elif console_content.endswith('\n\n'):
+ # Trim unwanted trailing newlines
+ console_content = console_content[:-1]
+ if banner_text is not None and 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
+ key_typeset = key_run + '#' + instance
+ typeset_cache[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('#', '@')),
+ r'\\begin{{pytx@SaveVerbatim}}[\1]{{pytx@{0}}}'.format(key_typeset.replace('#', '@')),
processed, count=1)
- processed = processed.rsplit('\\', 1)[0] + '\\end{SaveVerbatim}\n\n'
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@SaveVerbatim}\n\n'
pygments_macros[key_typeset].append(processed)
else:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@Verbatim}}[\1]{{pytx@{0}}}'.format(key_typeset.replace('#', '@')),
+ processed, count=1)
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@Verbatim}\n\n'
fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.pygtex')
- f = open(fname, 'w', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(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')
+ processed = ('\\begin{{pytx@SaveVerbatim}}{{pytx@{0}}}\n'.format(key_typeset.replace('#', '@')) +
+ console_content + '\\end{pytx@SaveVerbatim}\n\n')
macros.append(processed)
else:
- processed = ('\\begin{Verbatim}\n' + console_content +
- '\\end{Verbatim}\n\n')
+ processed = ('\\begin{pytx@Verbatim}\n' + console_content +
+ '\\end{pytx@Verbatim}\n\n')
fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.tex')
- f = open(fname, 'w', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
f.write(processed)
f.close()
files.append(fname)
@@ -2248,7 +2532,7 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
-def main():
+def main(python=None):
# Create dictionaries for storing data.
#
# All data that must be saved for subsequent runs is stored in "data".
@@ -2261,9 +2545,9 @@ def main():
# 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()
-
+ temp_data = {'errors': 0, 'warnings': 0, 'python': python}
+ old_data = dict()
+
# Process command-line options.
#
@@ -2300,8 +2584,8 @@ def main():
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'])
+ if not os.path.isdir(os.path.expanduser(os.path.normcase(data['settings']['outputdir']))):
+ os.mkdir(os.path.expanduser(os.path.normcase(data['settings']['outputdir'])))
# Load/create old_data
@@ -2322,6 +2606,10 @@ def main():
# Execute the code and perform Pygments highlighting via multiprocessing.
do_multiprocessing(data, temp_data, old_data, engine_dict)
+ # Skip exit message if in debug more
+ # #### May want to refactor
+ if temp_data['debug'] is not None or temp_data['interactive'] is not None:
+ sys.exit()
# Print exit message
print('\n--------------------------------------------------')
@@ -2357,4 +2645,12 @@ def main():
# 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
+ #// Python 2
+ if sys.version_info.major != 2:
+ sys.exit('This version of the PythonTeX script requires Python 2.')
+ #\\ End Python 2
+ #// Python 3
+ #if sys.version_info.major != 3:
+ # sys.exit('This version of the PythonTeX script requires Python 3.')
+ #\\ End Python 3
+ main(python=sys.version_info.major)
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex3.py b/Master/texmf-dist/scripts/pythontex/pythontex3.py
index 1712e75e096..1129b483f7b 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex3.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex3.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
@@ -13,7 +13,7 @@ should be in the same directory.
Licensed under the BSD 3-Clause License:
-Copyright (c) 2012-2013, Geoffrey M. Poore
+Copyright (c) 2012-2014, Geoffrey M. Poore
All rights reserved.
@@ -61,6 +61,7 @@ import multiprocessing
from pygments.styles import get_all_styles
from pythontex_engines import *
import textwrap
+import platform
if sys.version_info[0] == 2:
try:
@@ -76,7 +77,7 @@ else:
# Script parameters
# Version
-version = 'v0.12'
+version = 'v0.13'
@@ -84,35 +85,36 @@ 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.family, self.session, self.restart, self.instance, self.command, self.context, self.args_run, self.args_prettyprint, self.input_file, self.line = self.delims.split('#')
+ self.instance_int = int(self.instance)
+ self.line_int = int(self.line)
+ self.key_run = self.family + '#' + self.session + '#' + self.restart
+ self.key_typeset = self.key_run + '#' + self.instance
+ self.hashable_delims_run = self.key_typeset + '#' + self.command + '#' + self.context + '#' + self.args_run
+ self.hashable_delims_typeset = self.key_typeset + '#' + self.command + '#' + self.context + '#' + self.args_run
+ if len(self.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)
+ self.line_int += 1
+ self.line = str(self.line_int)
else:
self.is_inline = True
- self.is_extfile = True if self.input_session.startswith('EXT:') else False
+ self.is_extfile = True if self.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
+ self.extfile = os.path.expanduser(os.path.normcase(self.session.replace('EXT:', '', 1)))
+ self.key_typeset = self.key_typeset.replace('EXT:', '')
+ self.is_cc = True if self.family.startswith('CC:') else False
+ self.is_pyg = True if self.family.startswith('PYG') else False
+ self.is_verb = True if self.restart.endswith('verb') else False
if self.is_cc:
- self.input_instance += 'CC'
- self.cc_type, self.cc_pos = self.input_family.split(':')[1:]
+ self.instance += 'CC'
+ self.cc_type, self.cc_pos = self.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_cons = engine_dict[self.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):
+ if self.command in ('c', 'code') or (self.command == 'i' and not self.is_cons):
self.is_typeset = False
else:
self.is_typeset = True
@@ -142,19 +144,30 @@ def process_argv(data, temp_data):
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')
+ group_run = parser.add_mutually_exclusive_group()
+ group_run.add_argument('--runall', nargs='?', default='false',
+ const='true', choices=('true', 'false'),
+ help='run ALL code; equivalent to package option')
+ group_run.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('-j', '--jobs', metavar='N', default=None, type=int,
+ help='Allow N jobs at once; defaults to cpu_count().')
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')
+ group_debug = parser.add_mutually_exclusive_group()
+ group_debug.add_argument('--debug', nargs='?', default=None,
+ const='default',
+ metavar='<family>:<session>:<restart>',
+ help='Run the specified session (or default session) with the default debugger, if available. If there is only one session, it need not be specified. If the session name is unambiguous, it is sufficient. The full <family>:<session>:<restart> (for example, py:default:default) is only needed when the session name alone would be ambiguous.')
+ group_debug.add_argument('--interactive', nargs='?', default=None,
+ const='default',
+ metavar='<family>:<session>:<restart>',
+ help='Run the specified session (or default session) in interactive mode. If there is only one session, it need not be specified. If the session name is unambiguous, it is sufficient. The full <family>:<session>:<restart> (for example, py:default:default) is only needed when the session name alone would be ambiguous.')
args = parser.parse_args()
# Store the parsed argv in data and temp_data
@@ -174,8 +187,19 @@ def process_argv(data, temp_data):
temp_data['hashdependencies'] = True
else:
temp_data['hashdependencies'] = False
+ if args.jobs is None:
+ try:
+ jobs = multiprocessing.cpu_count()
+ except NotImplementedError:
+ jobs = 1
+ temp_data['jobs'] = jobs
+ else:
+ temp_data['jobs'] = args.jobs
temp_data['verbose'] = args.verbose
+ temp_data['debug'] = args.debug
+ temp_data['interactive'] = args.interactive
# Update interpreter_dict based on interpreter
+ set_python_interpreter = False
if args.interpreter is not None:
interp_list = args.interpreter.lstrip('{').rstrip('}').split(',')
for interp in interp_list:
@@ -185,10 +209,65 @@ def process_argv(data, temp_data):
k = k.strip(' \'"')
v = v.strip(' \'"')
interpreter_dict[k] = v
+ if k == 'python':
+ set_python_interpreter = True
except:
print('Invalid --interpreter argument')
return sys.exit(2)
-
+ # If the Python interpreter wasn't set, then try to set an appropriate
+ # default value, based on how PythonTeX was launched (pythontex.py,
+ # pythontex2.py, or pythontex3.py).
+ if not set_python_interpreter:
+ if temp_data['python'] == 2:
+ if platform.system() == 'Windows':
+ try:
+ subprocess.check_output(['py', '--version'])
+ interpreter_dict['python'] = 'py -2'
+ except:
+ msg = '''
+ * PythonTeX error:
+ You have launched PythonTeX using pythontex{0}.py
+ directly. This should only be done when you want
+ to use Python version {0}, but have a different
+ version installed as the default. (Otherwise, you
+ should start PythonTeX with pythontex.py.) For
+ this to work correctly, you should install Python
+ version 3.3+, which has a Windows wrapper (py) that
+ PythonTeX can use to run the correct version of
+ Python. If you do not want to install Python 3.3+,
+ you can also use the --interpreter command-line
+ option to tell PythonTeX how to access the version
+ of Python you wish to use.
+ '''.format(temp_data['python'])
+ print(textwrap.dedent(msg[1:]))
+ return sys.exit(2)
+ else:
+ interpreter_dict['python'] = 'python2'
+ elif temp_data['python'] == 3:
+ if platform.system() == 'Windows':
+ try:
+ subprocess.check_output(['py', '--version'])
+ interpreter_dict['python'] = 'py -3'
+ except:
+ msg = '''
+ * PythonTeX error:
+ You have launched PythonTeX using pythontex{0}.py
+ directly. This should only be done when you want
+ to use Python version {0}, but have a different
+ version installed as the default. (Otherwise, you
+ should start PythonTeX with pythontex.py.) For
+ this to work correctly, you should install Python
+ version 3.3+, which has a Windows wrapper (py) that
+ PythonTeX can use to run the correct version of
+ Python. If you do not want to install Python 3.3+,
+ you can also use the --interpreter command-line
+ option to tell PythonTeX how to access the version
+ of Python you wish to use.
+ '''.format(temp_data['python'])
+ print(textwrap.dedent(msg[1:]))
+ return sys.exit(2)
+ else:
+ interpreter_dict['python'] = 'python3'
if args.TEXNAME is not None:
# Determine if we a dealing with just a filename, or a name plus
@@ -332,7 +411,7 @@ def load_code_get_settings(data, temp_data):
else:
settings[k] = v
def set_kv_pygments(k, v):
- input_family, lexer_opts, options = v.replace(' ','').split('|')
+ family, lexer_opts, options = v.replace(' ','').split('|')
lexer = None
lex_dict = {}
opt_dict = {}
@@ -358,7 +437,7 @@ def load_code_get_settings(data, temp_data):
k = option
v = True
opt_dict[k] = v
- if input_family != ':GLOBAL':
+ if family != ':GLOBAL':
if 'lexer' in pygments_settings[':GLOBAL']:
lexer = pygments_settings[':GLOBAL']['lexer']
lex_dict.update(pygments_settings[':GLOBAL']['lexer_options'])
@@ -367,9 +446,9 @@ def load_code_get_settings(data, temp_data):
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
+ pygments_settings[family]['lexer'] = lexer
+ pygments_settings[family]['lexer_options'] = lex_dict
+ pygments_settings[family]['formatter_options'] = opt_dict
settings_func['version'] = set_kv_data
settings_func['outputdir'] = set_kv_data
settings_func['workingdir'] = set_kv_data
@@ -468,7 +547,7 @@ def get_old_data(data, old_data, temp_data):
'''
# Create a string containing the name of the data file
- pythontex_data_file = os.path.join(data['settings']['outputdir'], 'pythontex_data.pkl')
+ pythontex_data_file = os.path.expanduser(os.path.normcase(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):
@@ -498,14 +577,15 @@ def get_old_data(data, old_data, temp_data):
temp_data['loaded_old_data'] = False
# Set the utilspath
+ # Assume that if the utils aren't in the same location as
+ # `pythontex.py`, then they are somewhere else on `sys.path` that
+ # will always be available (for example, installed as a Python module),
+ # and thus specifying a path isn't necessary.
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)
+ data['utilspath'] = ''
@@ -525,7 +605,7 @@ def modified_dependencies(key, data, old_data, temp_data):
# 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)
+ dep_file = os.path.expanduser(os.path.normcase(os.path.join(workingdir, dep_file)))
if not os.path.isfile(dep_file):
print('* PythonTeX error')
print(' Cannot find dependency "' + dep + '"')
@@ -544,9 +624,9 @@ def modified_dependencies(key, data, old_data, temp_data):
# would require an unnecessary decoding and encoding cycle.
f = open(dep_file, 'rb')
hasher = sha1()
- hash = hasher(f.read()).hexdigest()
+ h = hasher(f.read()).hexdigest()
f.close()
- if hash != old_dep_hash_dict[dep][1]:
+ if h != old_dep_hash_dict[dep][1]:
return True
else:
mtime = os.path.getmtime(dep_file)
@@ -625,6 +705,7 @@ def hash_all(data, temp_data, old_data, engine_dict):
if c.is_typeset:
typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding))
typeset_hasher[c.key_typeset].update(code_encoded)
+ typeset_hasher[c.key_typeset].update(c.args_prettyprint.encode(encoding))
elif c.is_cons:
cons_hasher[c.key_run].update(c.hashable_delims_run.encode(encoding))
code_encoded = c.code.encode(encoding)
@@ -632,29 +713,31 @@ def hash_all(data, temp_data, old_data, engine_dict):
if c.is_typeset:
typeset_hasher[c.key_typeset].update(c.hashable_delims_typeset.encode(encoding))
typeset_hasher[c.key_typeset].update(code_encoded)
+ typeset_hasher[c.key_typeset].update(c.args_prettyprint.encode(encoding))
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))
+ typeset_hasher[c.key_typeset].update(c.args_prettyprint.encode(encoding))
# Store hashes
code_hash_dict = {}
for key in code_hasher:
- input_family = key.split('#', 1)[0]
+ family = key.split('#', 1)[0]
code_hash_dict[key] = (code_hasher[key].hexdigest(),
- cc_hasher[input_family].hexdigest(),
- engine_dict[input_family].get_hash())
+ cc_hasher[family].hexdigest(),
+ engine_dict[family].get_hash())
data['code_hash_dict'] = code_hash_dict
cons_hash_dict = {}
for key in cons_hasher:
- input_family = key.split('#', 1)[0]
+ family = key.split('#', 1)[0]
cons_hash_dict[key] = (cons_hasher[key].hexdigest(),
- cc_hasher[input_family].hexdigest(),
- engine_dict[input_family].get_hash())
+ cc_hasher[family].hexdigest(),
+ engine_dict[family].get_hash())
data['cons_hash_dict'] = cons_hash_dict
typeset_hash_dict = {}
@@ -741,9 +824,9 @@ def hash_all(data, temp_data, old_data, engine_dict):
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
+ family = key.split('#', 1)[0]
+ if family in pygments_settings:
+ if (not pygments_settings_changed[family] and
key in old_typeset_hash_dict and
typeset_hash_dict[key] == old_typeset_hash_dict[key]):
pygments_update[key] = False
@@ -768,8 +851,8 @@ def hash_all(data, temp_data, old_data, engine_dict):
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:
+ family = key.split('#', 1)[0]
+ if family in pygments_settings:
pygments_update[key] = True
else:
pygments_update[key] = False
@@ -866,6 +949,57 @@ def parse_code_write_scripts(data, temp_data, engine_dict):
cons_update = temp_data['cons_update']
pygments_update = temp_data['pygments_update']
files = data['files']
+ debug = temp_data['debug']
+ interactive = temp_data['interactive']
+
+ # Tweak the update dicts to work with debug command-line option.
+ # #### This should probably be refactored later, once the debug interface
+ # stabilizes
+ if debug is not None or interactive is not None:
+ if debug is not None:
+ arg = debug
+ else:
+ arg = interactive
+ for k in cons_update:
+ cons_update[k] = False
+ if ':' in arg:
+ # May need to refine in light of substitution of `:` -> `_`
+ # in session names?
+ arg_key = arg.replace(':', '#')
+ if arg_key not in code_update:
+ return sys.exit('Session {0} does not exist'.format(arg))
+ else:
+ for k in code_update:
+ code_update[k] = False
+ code_update[arg_key] = True
+ if debug is not None:
+ temp_data['debug_key'] = arg_key
+ else:
+ temp_data['interactive_key'] = arg_key
+ else:
+ session_count_dict = defaultdict(list)
+ for k in code_update:
+ s = k.split('#')[1]
+ session_count_dict[s].append(k)
+ if arg not in session_count_dict:
+ if arg in cons_update:
+ return sys.exit('Console sessions are not currently supported for interactive mode.')
+ else:
+ return sys.exit('Session "{0}" does not exist.'.format(arg))
+ elif len(session_count_dict[arg]) > 1:
+ return sys.exit('Ambiguous session name "{0}"; please specify <family>:<session>:<restart>'.format(arg))
+ else:
+ for k in code_update:
+ code_update[k] = False
+ arg_key = session_count_dict[arg][0]
+ code_update[arg_key] = True
+ if debug is not None:
+ temp_data['debug_key'] = arg_key
+ else:
+ temp_data['interactive_key'] = arg_key
+
+
+
# 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
@@ -874,8 +1008,8 @@ def parse_code_write_scripts(data, temp_data, engine_dict):
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.instance_int > last_instance[c.key_run]:
+ last_instance[c.key_run] = c.instance_int
if c.is_code:
if code_update[c.key_run]:
code_dict[c.key_run].append(c)
@@ -908,21 +1042,62 @@ def parse_code_write_scripts(data, temp_data, engine_dict):
# 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)
+ family, session, restart = key.split('#')
+ fname = os.path.join(outputdir, family + '_' + session + '_' + restart + '.' + engine_dict[family].extension)
+ # Want to keep track of files without expanding user, but need to
+ # expand user when actually writing files
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])
+ sessionfile = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
+ script, code_index = engine_dict[family].get_script(encoding,
+ utilspath,
+ outputdir,
+ workingdir,
+ cc_dict_begin[family],
+ code_dict[key],
+ cc_dict_end[family],
+ debug,
+ interactive)
for lines in script:
sessionfile.write(lines)
sessionfile.close()
code_index_dict[key] = code_index
temp_data['code_index_dict'] = code_index_dict
+
+ # Write synchronization file if in debug mode
+ if debug is not None:
+ # Might improve tracking/cleanup of syncdb files
+ key = temp_data['debug_key']
+ family, session, restart = key.split('#')
+ basename = key.replace('#', '_')
+ syncdb_fname = os.path.join(outputdir, basename + '.' + engine_dict[family].extension + '.syncdb')
+ files[key].append(syncdb_fname)
+ # #### In future version, try to use currfile to get this information
+ # automatically via the .pytxcode
+ main_doc_fname = None
+ for ext in ('.tex', '.ltx', '.dtx'):
+ if os.path.isfile(data['raw_jobname'] + ext):
+ main_doc_fname = data['raw_jobname'] + ext
+ break
+ if not main_doc_fname:
+ return sys.exit('Could not determine extension for main file "{0}"'.format(data['raw_jobname']))
+ main_code_fname = basename + '.' + engine_dict[family].extension
+ f = open(os.path.expanduser(os.path.normcase(syncdb_fname)), 'w', encoding='utf8')
+ f.write('{0},,{1},,\n'.format(main_code_fname, main_doc_fname))
+ # All paths are relative to the main code file. So if there is ever
+ # an option for creating other code files, in other locations, then
+ # the relative paths to those files will need to be specified.
+ for e in code_index_dict[key].values():
+ # #### Probably redo approach so this conversion isn't needed
+ if not e.input_file:
+ input_file = main_doc_fname
+ else:
+ input_file = e.input_file
+ if ',' in input_file or ',' in main_code_fname:
+ line = '"{0}",{1},"{2}",{3},{4}\n'.format(main_code_fname, e.lines_total+1, input_file, e.line_int, e.lines_input)
+ else:
+ line = '{0},{1},{2},{3},{4}\n'.format(main_code_fname, e.lines_total+1, input_file, e.line_int, e.lines_input)
+ f.write(line)
+ f.close()
@@ -935,6 +1110,7 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
keeptemps = data['settings']['keeptemps']
fvextfile = data['settings']['fvextfile']
pygments_settings = data['pygments_settings']
+ jobs = temp_data['jobs']
verbose = temp_data['verbose']
code_dict = temp_data['code_dict']
@@ -961,87 +1137,165 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
dependencies = data['dependencies']
exit_status = data['exit_status']
start_time = data['start_time']
+ debug = temp_data['debug']
+ interactive = temp_data['interactive']
+
+ # If in debug or interactive mode, short-circuit the whole process
+ # #### This should probably be refactored later, once debugging is more
+ # mature
+ if debug is not None or interactive is not None:
+ import shlex
+ if debug is not None:
+ print('Entering debug mode for "{0}"\n'.format(debug) + '-'*20 + '\n')
+ key = temp_data['debug_key']
+ else:
+ print('Entering interactive mode for "{0}"\n'.format(interactive) + '-'*20 + '\n')
+ key = temp_data['interactive_key']
+ basename = key.replace('#', '_')
+ family, session, restart = key.split('#')
+ # #### Revise as debugging is expanded
+ if debug is not None and engine_dict[family].language != 'python':
+ return sys.exit('Currently, debug only supports Python')
+ if debug is not None:
+ # #### Eventually, should move to pythontex_engines.py and
+ # provide means for customization
+ command = '{python} {debug} {file}.py --interactive'
+ command = command.replace('{python}', interpreter_dict['python'])
+ command = command.replace('{debug}', '"{0}"'.format(os.path.join(sys.path[0], 'syncpdb.py')))
+ else:
+ command = engine_dict[family].command + ' --interactive'
+ # Need to be in script directory so that pdb and any other tools that
+ # expect this will function correctly.
+ orig_cwd = os.getcwd()
+ if outputdir:
+ os.chdir(os.path.expanduser(os.path.normcase(outputdir)))
+ # Note that command is a string, which must be converted to list
+ # Must double-escape any backslashes so that they survive `shlex.split()`
+ script = basename
+ if os.path.isabs(os.path.expanduser(os.path.normcase(outputdir))):
+ script_full = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename)))
+ else:
+ script_full = os.path.expanduser(os.path.normcase(os.path.join(orig_cwd, 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('\\', '\\\\'), File=script_full.replace('\\', '\\\\'))))
+ exec_cmd = [unicode(elem) for elem in exec_cmd]
+ else:
+ exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\'), File=script_full.replace('\\', '\\\\')))
+ try:
+ proc = subprocess.Popen(exec_cmd)
+ 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)
+ else:
+ raise
+ proc.wait()
+ os.chdir(orig_cwd)
+ # Do a basic update of pickled data
+ # This is only really needed for tracking the code file and the
+ # synchronization file (if it was created)
+ if temp_data['loaded_old_data'] and key in old_data['exit_status']:
+ exit_status[key] = old_data['exit_status'][key]
+ else:
+ exit_status[key] = (None, None)
+ if temp_data['loaded_old_data']:
+ data['last_new_file_time'] = old_data['last_new_file_time']
+ else:
+ data['last_new_file_time'] = start_time
+ pythontex_data_file = os.path.expanduser(os.path.normcase(os.path.join(outputdir, 'pythontex_data.pkl')))
+ f = open(pythontex_data_file, 'wb')
+ pickle.dump(data, f, -1)
+ f.close()
+ return
- # 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)
+ # Create a pool for multiprocessing. Set the maximum number of
+ # concurrent processes to a user-specified value for jobs. If the user
+ # has not specified a value, then it will be None, and
+ # multiprocessing.Pool() will use cpu_count().
+ pool = multiprocessing.Pool(jobs)
tasks = []
# If verbose, print a list of processes
if verbose:
- print('\n* PythonTeX will run the following processes:')
+ print('\n* PythonTeX will run the following processes')
+ print(' with working directory {0}'.format(workingdir))
+ print(' (maximum concurrent processes = {0})'.format(jobs))
# 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]
+ 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,
+ engine_dict[family].language,
+ engine_dict[family].command,
+ engine_dict[family].created,
+ engine_dict[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,
+ engine_dict[family].errors,
+ engine_dict[family].warnings,
+ engine_dict[family].linenumbers,
+ engine_dict[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,
+ engine_dict[family].language,
+ engine_dict[family].command,
+ engine_dict[family].created,
+ engine_dict[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,
+ engine_dict[family].errors,
+ engine_dict[family].warnings,
+ engine_dict[family].linenumbers,
+ engine_dict[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:
+ family = key.split('#')[0]
+ if engine_dict[family].language.startswith('python'):
+ if 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)'''
+ fvextfile, pygments_settings[family],
+ cc_dict_begin[family], cons_dict[key],
+ cc_dict_end[family], engine_dict[family].startup,
+ engine_dict[family].banner,
+ engine_dict[family].filename)'''
tasks.append(pool.apply_async(python_console, [jobname, encoding,
outputdir, workingdir,
fvextfile,
- pygments_settings[input_family],
- cc_dict_begin[input_family],
+ pygments_settings[family],
+ cc_dict_begin[family],
cons_dict[key],
- cc_dict_end[input_family],
- engine_dict[input_family].startup,
- engine_dict[input_family].banner,
- engine_dict[input_family].filename]))
+ cc_dict_end[family],
+ engine_dict[family].startup,
+ engine_dict[family].banner,
+ engine_dict[family].filename]))
else:
tasks.append(pool.apply_async(python_console, [jobname, encoding,
outputdir, workingdir,
fvextfile,
None,
- cc_dict_begin[input_family],
+ cc_dict_begin[family],
cons_dict[key],
- cc_dict_end[input_family],
- engine_dict[input_family].startup,
- engine_dict[input_family].banner,
- engine_dict[input_family].filename]))
+ cc_dict_end[family],
+ engine_dict[family].startup,
+ engine_dict[family].banner,
+ engine_dict[family].filename]))
else:
print('* PythonTeX error')
print(' Currently, non-Python consoles are not supported')
@@ -1113,7 +1367,7 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
# beginning of the run. If so, reset them so they will run next time and
# issue a warning
unresolved_dependencies = False
- unresolved_sessions= []
+ unresolved_sessions = []
for key in dependencies:
for dep, val in dependencies[key].items():
if val[0] > start_time:
@@ -1140,13 +1394,13 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
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 = open(os.path.expanduser(os.path.normcase(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)
+ pygments_macro_file = open(os.path.expanduser(os.path.normcase(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:
@@ -1156,7 +1410,7 @@ def do_multiprocessing(data, temp_data, old_data, engine_dict):
pygments_macro_file.write(''.join(pygments_macros[key]))
pygments_macro_file.close()
- pythontex_data_file = os.path.join(outputdir, 'pythontex_data.pkl')
+ pythontex_data_file = os.path.expanduser(os.path.normcase(os.path.join(outputdir, 'pythontex_data.pkl')))
f = open(pythontex_data_file, 'wb')
pickle.dump(data, f, -1)
f.close()
@@ -1183,8 +1437,8 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
import shlex
# Create what's needed for storing results
- input_family = code_list[0].input_family
- input_session = code_list[0].input_session
+ family = code_list[0].family
+ session = code_list[0].session
key_run = code_list[0].key_run
files = []
macros = []
@@ -1207,19 +1461,23 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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_name = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename + '.out')))
+ err_file_name = os.path.expanduser(os.path.normcase(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)
+ script = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename)))
+ if os.path.isabs(script):
+ script_full = script
+ else:
+ script_full = os.path.expanduser(os.path.normcase(os.path.join(os.getcwd(), 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 = shlex.split(bytes(command.format(file=script.replace('\\', '\\\\'), File=script_full.replace('\\', '\\\\'))))
exec_cmd = [unicode(elem) for elem in exec_cmd]
else:
- exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\')))
+ exec_cmd = shlex.split(command.format(file=script.replace('\\', '\\\\'), File=script_full.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:
@@ -1271,7 +1529,10 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if valid_stdout:
# Add created files to created list
for c in created.splitlines():
- files.append(c)
+ if os.path.isabs(os.path.expanduser(os.path.normcase(c))):
+ files.append(c)
+ else:
+ files.append(os.path.join(workingdir, c))
# Create a set of dependencies, to eliminate duplicates in the event
# that there are any. This is mainly useful when dependencies are
@@ -1283,7 +1544,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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)
+ dep_file = os.path.expanduser(os.path.normcase(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
@@ -1313,21 +1574,21 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if block:
delims, content = block.split('#\n', 1)
if content:
- input_instance, input_command = delims.split('#')
- if input_instance.endswith('CC'):
+ instance, command = delims.split('#')
+ if instance.endswith('CC'):
messages.append('* PythonTeX warning')
- messages.append(' Custom code for "' + input_family + '" attempted to print or write to stdout')
+ messages.append(' Custom code for "' + 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'
+ elif command == 'i':
+ content = r'\pytx@SVMCR{pytx@MCR@' + key_run.replace('#', '@') + '@' + 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)
+ fname = os.path.join(outputdir, basename + '_' + instance + '.stdout')
+ f = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
f.write(content)
f.close()
files.append(fname)
@@ -1361,7 +1622,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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)
+ # #### Consider os.path.normcase(), making search case-insensitive
+ outputdir_exp = os.path.expanduser(outputdir)
+ fullbasename_correct = os.path.join(outputdir_exp, basename)
if '\\' in fullbasename_correct:
fullbasename_reslashed = fullbasename_correct.replace('\\', '/')
else:
@@ -1401,9 +1664,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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)
+ doclinenum = str(index_now[1].line_int + index_now[1].lines_input)
else:
- doclinenum = str(index_now[1].input_line_int + errlinenum - index_now[1].lines_total - 1)
+ doclinenum = str(index_now[1].line_int + errlinenum - index_now[1].lines_total - 1)
input_file = index_now[1].input_file
else:
doclinenum = '??'
@@ -1469,7 +1732,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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'))
+ err_messages_ud.append(' ' + line.replace(outputdir_exp, '<outputdir>').rstrip('\n'))
else:
err_messages_ud.append(' ' + line.rstrip('\n'))
@@ -1513,7 +1776,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
process = False
else:
process = True
- if len(index_now[1].input_command) > 1:
+ if len(index_now[1].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:
@@ -1540,7 +1803,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if stderrfilename == 'full':
line = line.replace(fullbasename, basename)
elif stderrfilename == 'session':
- line = line.replace(fullbasename, input_session)
+ line = line.replace(fullbasename, session)
elif stderrfilename == 'genericfile':
line = line.replace(fullbasename + '.' + extension, '<file>')
elif stderrfilename == 'genericscript':
@@ -1567,9 +1830,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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
+ instance = last_delim.split('#')[1]
+ doclinenum = str(code_index[instance].line_int)
+ input_file = code_index[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
@@ -1630,13 +1893,13 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
pass
if found:
# Get info from last delim
- input_instance, input_command = last_delim.split('#')[1:-1]
+ instance, command = last_delim.split('#')[1:-1]
# Calculate the line number in the document
- ei = code_index[input_instance]
+ ei = code_index[instance]
if errlinenum > ei.lines_total + ei.lines_input:
- doclinenum = str(ei.input_line_int + ei.lines_input)
+ doclinenum = str(ei.line_int + ei.lines_input)
else:
- doclinenum = str(ei.input_line_int + errlinenum - ei.lines_total - 1)
+ doclinenum = str(ei.line_int + errlinenum - ei.lines_total - 1)
input_file = ei.input_file
else:
doclinenum = '??'
@@ -1704,9 +1967,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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')
+ line = line.replace(outputdir_exp, '<outputdir>').rstrip('\n')
if '/<outputdir>' in line or '\\<outputdir>' in line:
- line = sub(r'(?:(?:[A-Z]:\\)|(?:~?/)).*<outputdir>', '<outputdir>', line)
+ line = sub(r'(?:(?:[A-Za-z]:\\)|(?:~?/)).*<outputdir>', '<outputdir>', line)
msg.append(' ' + line)
else:
msg.append(' ' + line.rstrip('\n'))
@@ -1715,9 +1978,9 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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
+ instance = last_delim.split('#')[1]
+ doclinenum = str(code_index[instance].line_int)
+ input_file = code_index[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
@@ -1755,12 +2018,12 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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'):
+ instance, command = line.split('#')[1:-1]
+ if instance.endswith('CC'):
process = False
else:
process = True
- err_key = basename + '_' + input_instance
+ err_key = basename + '_' + instance
elif process and basename in line:
found = False
for pattern in linesig:
@@ -1773,14 +2036,14 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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
+ ei = code_index[instance]
+ # Store the `instance` in case it's
# incremented later
- last_input_instance = input_instance
+ last_instance = 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
+ # determine the correct 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
@@ -1790,25 +2053,25 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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]
+ next_instance = str(int(instance) + 1)
+ if next_instance in code_index:
+ next_ei = code_index[next_instance]
if errlinenum > next_ei.lines_total:
- input_instance = next_input_instance
+ instance = next_instance
ei = next_ei
else:
break
else:
break
- if len(input_command) > 1:
+ if len(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
+ # Reset `instance`, in case incremented
+ instance = last_instance
else:
codelinenum = '??'
messages.append('* PythonTeX notice')
@@ -1822,7 +2085,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
if stderrfilename == 'full':
line = line.replace(fullbasename, basename)
elif stderrfilename == 'session':
- line = line.replace(fullbasename, input_session)
+ line = line.replace(fullbasename, session)
elif stderrfilename == 'genericfile':
line = line.replace(fullbasename + '.' + extension, '<file>')
elif stderrfilename == 'genericscript':
@@ -1833,7 +2096,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
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 = open(os.path.expanduser(os.path.normcase(stderr_file_name)), 'w', encoding=encoding)
f.write(''.join(err_dict[err_key]))
f.close()
files.append(stderr_file_name)
@@ -1841,12 +2104,12 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
# 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)
+ fname = os.path.expanduser(os.path.normcase(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)
+ fname = os.path.expanduser(os.path.normcase(os.path.join(outputdir, basename + '.' + ext)))
if os.path.isfile(fname):
os.remove(fname)
files.append(os.path.join(outputdir, basename + '.' + extension))
@@ -1873,7 +2136,7 @@ def run_code(encoding, outputdir, workingdir, code_list, language, command,
unknowns_message = '''
* PythonTeX notice
{0} message(s) could not be classified
- Based on the return code, they were interpreted as {1}'''
+ Interpreted as {1}, based on the return code(s)'''
messages[0] += textwrap.dedent(unknowns_message.format(unknowns, unknowns_type))
# Take care of anything that has escaped detection thus far.
@@ -1940,7 +2203,7 @@ def do_pygments(encoding, outputdir, fvextfile, pygments_list,
# Actually parse and highlight the code.
for c in pygments_list:
if c.is_cons:
- content = typeset_cache[c.key_run][c.input_instance]
+ content = typeset_cache[c.key_run][c.instance]
elif c.is_extfile:
if os.path.isfile(c.extfile):
f = open(c.extfile, encoding=encoding)
@@ -1953,16 +2216,27 @@ def do_pygments(encoding, outputdir, fvextfile, pygments_list,
messages.append(' The file was not pygmentized')
else:
content = c.code
- processed = highlight(content, lexer[c.input_family], formatter[c.input_family])
+ processed = highlight(content, lexer[c.family], formatter[c.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'
+ if c.args_prettyprint:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@SaveVerbatim}}[\1, {4}]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance, c.args_prettyprint), processed, count=1)
+ else:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@SaveVerbatim}}[\1]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance), processed, count=1)
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@SaveVerbatim}\n\n'
pygments_macros[c.key_typeset].append(processed)
else:
+ if c.args_prettyprint:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@Verbatim}}[\1, {4}]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance, c.args_prettyprint), processed, count=1)
+ else:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@Verbatim}}[\1]{{pytx@{0}@{1}@{2}@{3}}}'.format(c.family, c.session, c.restart, c.instance), processed, count=1)
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@Verbatim}\n\n'
fname = os.path.join(outputdir, c.key_typeset.replace('#', '_') + '.pygtex')
- f = open(fname, 'w', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
f.write(processed)
f.close()
pygments_files[c.key_typeset].append(fname)
@@ -2052,19 +2326,19 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
if os.getcwd() not in sys.path:
sys.path.append(os.getcwd())
else:
- sys.exit('Cannot find directory {workingdir}')
+ 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:]
+ cons_config = cons_config.format(workingdir=os.path.expanduser(os.path.normcase(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.append('=>PYTHONTEX#{0}#{1}#\n'.format(c.instance, c.command))
self.console_code.extend(c.code.splitlines())
old_stdout = sys.stdout
sys.stdout = self.iostdout
@@ -2104,11 +2378,15 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
# isn't typeset
cons_index = {}
for c in cons_list:
- cons_index[c.input_instance] = c.input_line
+ cons_index[c.instance] = c.line
# Consolize the code
+ # If the working directory is changed as part of the console code,
+ # then we need to get back to where we were.
con = Console(banner, filename)
+ cwd = os.getcwd()
con.consolize(startup, cons_list)
+ os.chdir(cwd)
# Set up Pygments, if applicable
if pygments_settings is not None:
@@ -2134,8 +2412,8 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
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':
+ instance, command = delims.split('#')
+ if instance == 'STARTUP':
exception = False
console_content_lines = console_content.splitlines()
for line in console_content_lines:
@@ -2157,14 +2435,13 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
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'):
+ elif 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:
@@ -2177,15 +2454,15 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
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]))
+ if instance.endswith('CC'):
+ messages.append('* PythonTeX stderr - {0} near line {1} in custom code for console:'.format(alert_type, cons_index[instance]))
else:
- messages.append('* PythonTeX stderr - {0} near line {1} in console code:'.format(alert_type, cons_index[input_instance]))
+ messages.append('* PythonTeX stderr - {0} near line {1} in console code:'.format(alert_type, cons_index[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':
+ if 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.
@@ -2193,38 +2470,45 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
# 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':
+ elif console_content.endswith('\n\n'):
+ # Trim unwanted trailing newlines
+ console_content = console_content[:-1]
+ if banner_text is not None and 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
+ key_typeset = key_run + '#' + instance
+ typeset_cache[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('#', '@')),
+ r'\\begin{{pytx@SaveVerbatim}}[\1]{{pytx@{0}}}'.format(key_typeset.replace('#', '@')),
processed, count=1)
- processed = processed.rsplit('\\', 1)[0] + '\\end{SaveVerbatim}\n\n'
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@SaveVerbatim}\n\n'
pygments_macros[key_typeset].append(processed)
else:
+ processed = sub(r'\\begin{Verbatim}\[(.+)\]',
+ r'\\begin{{pytx@Verbatim}}[\1]{{pytx@{0}}}'.format(key_typeset.replace('#', '@')),
+ processed, count=1)
+ processed = processed.rsplit('\\', 1)[0] + '\\end{pytx@Verbatim}\n\n'
fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.pygtex')
- f = open(fname, 'w', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(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')
+ processed = ('\\begin{{pytx@SaveVerbatim}}{{pytx@{0}}}\n'.format(key_typeset.replace('#', '@')) +
+ console_content + '\\end{pytx@SaveVerbatim}\n\n')
macros.append(processed)
else:
- processed = ('\\begin{Verbatim}\n' + console_content +
- '\\end{Verbatim}\n\n')
+ processed = ('\\begin{pytx@Verbatim}\n' + console_content +
+ '\\end{pytx@Verbatim}\n\n')
fname = os.path.join(outputdir, key_typeset.replace('#', '_') + '.tex')
- f = open(fname, 'w', encoding=encoding)
+ f = open(os.path.expanduser(os.path.normcase(fname)), 'w', encoding=encoding)
f.write(processed)
f.close()
files.append(fname)
@@ -2248,7 +2532,7 @@ def python_console(jobname, encoding, outputdir, workingdir, fvextfile,
-def main():
+def main(python=None):
# Create dictionaries for storing data.
#
# All data that must be saved for subsequent runs is stored in "data".
@@ -2261,9 +2545,9 @@ def main():
# 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()
-
+ temp_data = {'errors': 0, 'warnings': 0, 'python': python}
+ old_data = dict()
+
# Process command-line options.
#
@@ -2300,8 +2584,8 @@ def main():
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'])
+ if not os.path.isdir(os.path.expanduser(os.path.normcase(data['settings']['outputdir']))):
+ os.mkdir(os.path.expanduser(os.path.normcase(data['settings']['outputdir'])))
# Load/create old_data
@@ -2322,6 +2606,10 @@ def main():
# Execute the code and perform Pygments highlighting via multiprocessing.
do_multiprocessing(data, temp_data, old_data, engine_dict)
+ # Skip exit message if in debug more
+ # #### May want to refactor
+ if temp_data['debug'] is not None or temp_data['interactive'] is not None:
+ sys.exit()
# Print exit message
print('\n--------------------------------------------------')
@@ -2357,4 +2645,12 @@ def main():
# 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
+ #// Python 2
+ #if sys.version_info.major != 2:
+ # sys.exit('This version of the PythonTeX script requires Python 2.')
+ #\\ End Python 2
+ #// Python 3
+ if sys.version_info.major != 3:
+ sys.exit('This version of the PythonTeX script requires Python 3.')
+ #\\ End Python 3
+ main(python=sys.version_info.major)
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py b/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py
index baff37b411d..166e6784676 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex_2to3.py
@@ -27,7 +27,7 @@ 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
+Copyright (c) 2012-2014, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
@@ -67,6 +67,8 @@ def from2to3(list_of_code):
elif in_3:
line = re.sub(indent + '#', indent, line, count=1)
fixed.append(line)
+ if fixed[0].startswith('#!/usr/bin/env python2'):
+ fixed[0] = fixed[0].replace('python2', 'python3')
return fixed
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_engines.py b/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
index 518820c8a7b..83ae0acd50e 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
@@ -17,7 +17,7 @@ document (script for execution).
-Copyright (c) 2012-2013, Geoffrey M. Poore
+Copyright (c) 2012-2014, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
@@ -25,25 +25,27 @@ Licensed under the BSD 3-Clause License:
'''
# Imports
+import os
import sys
import textwrap
from hashlib import sha1
from collections import OrderedDict, namedtuple
-interpreter_dict = {k:k for k in ('python', 'ruby', 'julia')}
+interpreter_dict = {k:k for k in ('python', 'ruby', 'julia', 'octave')}
# 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}'
+interpreter_dict['File'] = '{File}'
engine_dict = {}
-CodeIndex = namedtuple('CodeIndex', ['input_file', 'input_command',
- 'input_line_int', 'lines_total',
+CodeIndex = namedtuple('CodeIndex', ['input_file', 'command',
+ 'line_int', 'lines_total',
'lines_user', 'lines_input',
'inline_count'])
@@ -100,19 +102,13 @@ class CodeEngine(object):
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.
+ # Perform some additional formatting on some strings.
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))
+ self.template = self._dedent(self.template)
+ self.wrapper = self._dedent(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'
+ if not self.formatter.endswith('\n'):
+ self.formatter = self.formatter + '\n'
# Type check errors, warnings, and linenumbers
if errors is None:
@@ -158,7 +154,7 @@ class CodeEngine(object):
raise TypeError('CodeEngine needs "warnings" to contain strings')
self.warnings = warnings
if linenumbers is None:
- linenumbers = 'line {{number}}'
+ linenumbers = 'line {number}'
if sys.version_info[0] == 2:
if isinstance(linenumbers, basestring):
linenumbers = [linenumbers]
@@ -177,7 +173,7 @@ class CodeEngine(object):
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]
+ linenumbers = [l.replace('{number}', r'(\d+)') for l in linenumbers]
self.linenumbers = linenumbers
# Type check lookbehind
@@ -251,25 +247,6 @@ class CodeEngine(object):
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):
'''
@@ -290,6 +267,8 @@ class CodeEngine(object):
`__future__` be at the very beginning of a script.
'''
# Take care of `--interpreter`
+ # The `interpreter_dict` has entries that allow `{file}` and
+ # `{outputdir}` fields to be replaced with themselves
self.command = self.command.format(**interpreter_dict)
# Take care of `__future__`
if self.language.startswith('python'):
@@ -419,8 +398,8 @@ class CodeEngine(object):
else:
return cc_future + code_future
- def get_script(self, encoding, utilspath, workingdir,
- cc_list_begin, code_list, cc_list_end):
+ def get_script(self, encoding, utilspath, outputdir, workingdir,
+ cc_list_begin, code_list, cc_list_end, debug, interactive):
'''
Assemble the script that will be executed. In the process, assemble
an index of line numbers that may be used to correlate script line
@@ -441,15 +420,28 @@ class CodeEngine(object):
try:
script_begin, script_end = self.template.split('{body}')
except:
- raise ValueError('Template for ' + self.name + ' is missing {{body}}')
+ raise ValueError('Template for ' + self.name + ' is missing {body}')
# Add beginning to script
+ if os.path.isabs(os.path.expanduser(os.path.normcase(workingdir))):
+ workingdir_full = workingdir
+ else:
+ workingdir_full = os.path.join(os.getcwd(), workingdir).replace('\\', '/')
+ # Correct workingdir if in debug or interactive mode, so that it's
+ # relative to the script path
+ # #### May refactor this once debugging functionality is more
+ # fully implemented
+ if debug is not None or interactive is not None:
+ if not os.path.isabs(os.path.expanduser(os.path.normcase(workingdir))):
+ workingdir = os.path.relpath(workingdir, outputdir)
script_begin = script_begin.format(encoding=encoding, future=future,
- utilspath=utilspath, workingdir=workingdir,
+ utilspath=utilspath,
+ workingdir=os.path.expanduser(os.path.normcase(workingdir)),
+ Workingdir=workingdir_full,
extend=self.extend,
- input_family=code_list[0].input_family,
- input_session=code_list[0].input_session,
- input_restart=code_list[0].input_restart,
+ family=code_list[0].family,
+ session=code_list[0].session,
+ restart=code_list[0].restart,
dependencies_delim='=>PYTHONTEX:DEPENDENCIES#',
created_delim='=>PYTHONTEX:CREATED#')
script.append(script_begin)
@@ -459,7 +451,7 @@ class CodeEngine(object):
try:
wrapper_begin, wrapper_end = self.wrapper.split('{code}')
except:
- raise ValueError('Wrapper for ' + self.name + ' is missing {{code}}')
+ 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
@@ -472,9 +464,9 @@ class CodeEngine(object):
# 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)
+ stdoutdelim = '=>PYTHONTEX:STDOUT#{instance}#{command}#'
+ stderrdelim = '=>PYTHONTEX:STDERR#{instance}#{command}#'
+ wrapper_begin = wrapper_begin.replace('{stdoutdelim}', stdoutdelim).replace('{stderrdelim}', stderrdelim)
wrapper_begin_offset = wrapper_begin.count('\n')
wrapper_end_offset = wrapper_end.count('\n')
@@ -486,15 +478,15 @@ class CodeEngine(object):
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))
+ script.append(wrapper_begin.format(command=c.command,
+ context=c.context,
+ args=c.args_run,
+ instance=c.instance,
+ line=c.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)
+ code_index[c.instance] = CodeIndex(c.input_file, c.command, c.line_int, lines_total, lines_user, lines_input, inline_count)
script.append(c.code)
if c.is_inline:
inline_count += 1
@@ -511,16 +503,16 @@ class CodeEngine(object):
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))
+ script.append(wrapper_begin.format(command=c.command,
+ context=c.context,
+ args=c.args_run,
+ instance=c.instance,
+ line=c.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':
+ code_index[c.instance] = CodeIndex(c.input_file, c.command, c.line_int, lines_total, lines_user, lines_input, inline_count)
+ if c.command == 'i':
script.append(self.formatter.format(code=c.code.rstrip('\n')))
inline_count += 1
else:
@@ -538,15 +530,15 @@ class CodeEngine(object):
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))
+ script.append(wrapper_begin.format(command=c.command,
+ context=c.context,
+ args=c.args_run,
+ instance=c.instance,
+ line=c.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)
+ code_index[c.instance] = CodeIndex(c.input_file, c.command, c.line_int, lines_total, lines_user, lines_input, inline_count)
script.append(c.code)
if c.is_inline:
inline_count += 1
@@ -558,7 +550,7 @@ class CodeEngine(object):
lines_total += wrapper_end_offset
# Finish script
- script.append(script_end)
+ script.append(script_end.format(dependencies_delim='=>PYTHONTEX:DEPENDENCIES#', created_delim='=>PYTHONTEX:CREATED#'))
return script, code_index
@@ -625,67 +617,70 @@ class PythonConsoleEngine(CodeEngine):
python_template = '''
- # -*- coding: {{encoding}} -*-
-
- {{future}}
+ # -*- 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')
+ if '--interactive' not in sys.argv[1:]:
+ 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}}')
+ if '{utilspath}' and '{utilspath}' not in sys.path:
+ 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.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}}')
+ sys.exit('Cannot find directory {workingdir}')
if pytex.docdir not in sys.path:
sys.path.append(pytex.docdir)
- {{extend}}
+ {extend}
- pytex.input_family = '{{input_family}}'
- pytex.input_session = '{{input_session}}'
- pytex.input_restart = '{{input_restart}}'
+ pytex.id = '{family}_{session}_{restart}'
+ pytex.family = '{family}'
+ pytex.session = '{session}'
+ pytex.restart = '{restart}'
- {{body}}
+ {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.command = '{command}'
+ pytex.set_context('{context}')
+ pytex.args = '{args}'
+ pytex.instance = '{instance}'
+ pytex.line = '{line}'
+
+ print('{stdoutdelim}')
+ sys.stderr.write('{stderrdelim}\\n')
pytex.before()
- {{code}}
+ {code}
pytex.after()
'''
-CodeEngine('python', 'python', '.py', '{{python}} {{file}}.py',
- python_template, python_wrapper, 'print(pytex.formatter({{code}}))',
- 'Error:', 'Warning:', ['line {{number}}', ':{{number}}:'])
+CodeEngine('python', 'python', '.py', '{python} {file}.py',
+ python_template, python_wrapper, 'print(pytex.formatter({code}))',
+ 'Error:', 'Warning:', ['line {number}', ':{number}:'])
SubCodeEngine('python', 'py')
@@ -711,18 +706,22 @@ PythonConsoleEngine('sympycon', startup='from sympy import *')
ruby_template = '''
- # -*- coding: {{encoding}} -*-
+ # -*- coding: {encoding} -*-
- $stdout.set_encoding('{{encoding}}')
- $stderr.set_encoding('{{encoding}}')
+ unless ARGV.include?('--interactive')
+ $stdout.set_encoding('{encoding}')
+ $stderr.set_encoding('{encoding}')
+ end
class RubyTeXUtils
- attr_accessor :input_family, :input_session, :input_restart,
- :input_command, :input_context, :input_args,
- :input_instance, :input_line, :dependencies, :created, :docdir
+ attr_accessor :id, :family, :session, :restart,
+ :command, :context, :args,
+ :instance, :line, :dependencies, :created,
+ :docdir, :_context_raw
def initialize
@dependencies = Array.new
@created = Array.new
+ @_context_raw = nil
end
def formatter(expr)
return expr.to_s
@@ -737,14 +736,39 @@ ruby_template = '''
def add_created(*expr)
self.created.push(*expr)
end
+ def set_context(expr)
+ if expr != "" and expr != @_context_raw
+ @context = expr.split(',').map{{|x| x1,x2 = x.split('='); {{x1.strip() => x2.strip()}}}}.reduce(:merge)
+ @_context_raw = expr
+ end
+ end
+ def pt_to_in(expr)
+ if expr.is_a?String
+ if expr.end_with?'pt'
+ expr = expr[0..-3]
+ end
+ return expr.to_f/72.27
+ else
+ return expr/72.27
+ end
+ end
+ def pt_to_cm(expr)
+ return pt_to_in(expr)*2.54
+ end
+ def pt_to_mm(expr)
+ return pt_to_in(expr)*25.4
+ end
+ def pt_to_bp(expr)
+ return pt_to_in(expr)*72
+ end
def cleanup
- puts '{{dependencies_delim}}'
+ puts '{dependencies_delim}'
if @dependencies
- @dependencies.each { |x| puts x }
+ @dependencies.each {{ |x| puts x }}
end
- puts '{{created_delim}}'
+ puts '{created_delim}'
if @created
- @created.each { |x| puts x }
+ @created.each {{ |x| puts x }}
end
end
end
@@ -752,44 +776,45 @@ ruby_template = '''
rbtex = RubyTeXUtils.new
rbtex.docdir = Dir.pwd
- if File.directory?('{{workingdir}}')
- Dir.chdir('{{workingdir}}')
+ 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}}')
+ abort('Cannot change to directory {workingdir}')
end
$LOAD_PATH.push(rbtex.docdir) unless $LOAD_PATH.include?(rbtex.docdir)
- {{extend}}
+ {extend}
- rbtex.input_family = '{{input_family}}'
- rbtex.input_session = '{{input_session}}'
- rbtex.input_restart = '{{input_restart}}'
+ rbtex.id = '{family}_{session}_{restart}'
+ rbtex.family = '{family}'
+ rbtex.session = '{session}'
+ rbtex.restart = '{restart}'
- {{body}}
+ {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.command = '{command}'
+ rbtex.set_context('{context}')
+ rbtex.args = '{args}'
+ rbtex.instance = '{instance}'
+ rbtex.line = '{line}'
+
+ puts '{stdoutdelim}'
+ $stderr.puts '{stderrdelim}'
rbtex.before
- {{code}}
+ {code}
rbtex.after
'''
-CodeEngine('ruby', 'ruby', '.rb', '{{ruby}} {{file}}.rb', ruby_template,
- ruby_wrapper, 'puts rbtex.formatter({{code}})',
- ['Error)', '(Errno', 'error'], 'warning:', ':{{number}}:')
+CodeEngine('ruby', 'ruby', '.rb', '{ruby} {file}.rb', ruby_template,
+ ruby_wrapper, 'puts rbtex.formatter({code})',
+ ['Error)', '(Errno', 'error'], 'warning:', ':{number}:')
SubCodeEngine('ruby', 'rb')
@@ -803,24 +828,31 @@ julia_template = '''
# 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
+ id::String
+ family::String
+ session::String
+ restart::String
+ command::String
+ context::Dict
+ args::String
+ instance::String
+ line::String
- _dependencies::Array{String}
- _created::Array{String}
+ _dependencies::Array{{String}}
+ _created::Array{{String}}
docdir::String
+ _context_raw::String
formatter::Function
before::Function
after::Function
add_dependencies::Function
add_created::Function
+ set_context::Function
+ pt_to_in::Function
+ pt_to_cm::Function
+ pt_to_mm::Function
+ pt_to_bp::Function
cleanup::Function
self::JuliaTeXUtils
@@ -830,6 +862,7 @@ julia_template = '''
self.self = self
self._dependencies = Array(String, 0)
self._created = Array(String, 0)
+ self._context_raw = ""
function formatter(expr)
string(expr)
@@ -854,12 +887,47 @@ julia_template = '''
end
self.add_created = add_created
+ function set_context(expr)
+ if expr != "" && expr != self._context_raw
+ self.context = {{strip(x[1]) => strip(x[2]) for x in map(x -> split(x, "="), split(expr, ","))}}
+ self._context_raw = expr
+ end
+ end
+ self.set_context = set_context
+
+ function pt_to_in(expr)
+ if isa(expr, String)
+ if sizeof(expr) > 2 && expr[end-1:end] == "pt"
+ expr = expr[1:end-2]
+ end
+ return float(expr)/72.27
+ else
+ return expr/72.27
+ end
+ end
+ self.pt_to_in = pt_to_in
+
+ function pt_to_cm(expr)
+ return self.pt_to_in(expr)*2.54
+ end
+ self.pt_to_cm = pt_to_cm
+
+ function pt_to_mm(expr)
+ return self.pt_to_in(expr)*25.4
+ end
+ self.pt_to_mm = pt_to_mm
+
+ function pt_to_bp(expr)
+ return self.pt_to_in(expr)*72
+ end
+ self.pt_to_bp = pt_to_bp
+
function cleanup()
- println("{{dependencies_delim}}")
+ println("{dependencies_delim}")
for f in self._dependencies
println(f)
end
- println("{{created_delim}}")
+ println("{created_delim}")
for f in self._created
println(f)
end
@@ -874,49 +942,191 @@ julia_template = '''
jltex.docdir = pwd()
try
- cd("{{workingdir}}")
- if !(contains(LOAD_PATH, pwd()))
- push!(LOAD_PATH, pwd())
- end
+ cd("{workingdir}")
catch
if !(length(ARGS) > 0 && ARGS[1] == "--manual")
- error("Could not find directory {{workingdir}}")
+ error("Could not find directory {workingdir}")
end
end
- if !(contains(LOAD_PATH, jltex.docdir))
+ if !(in(jltex.docdir, LOAD_PATH))
push!(LOAD_PATH, jltex.docdir)
end
- {{extend}}
+ {extend}
- jltex.input_family = "{{input_family}}"
- jltex.input_session = "{{input_session}}"
- jltex.input_restart = "{{input_restart}}"
+ jltex.id = "{family}_{session}_{restart}"
+ jltex.family = "{family}"
+ jltex.session = "{session}"
+ jltex.restart = "{restart}"
- {{body}}
+ {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.command = "{command}"
+ jltex.set_context("{context}")
+ jltex.args = "{args}"
+ jltex.instance = "{instance}"
+ jltex.line = "{line}"
+
+ println("{stdoutdelim}")
+ write(STDERR, "{stderrdelim}\\n")
jltex.before()
- {{code}}
+ {code}
jltex.after()
'''
-CodeEngine('julia', 'julia', '.jl', '{{julia}} {{file}}.jl', julia_template,
- julia_wrapper, 'println(jltex.formatter({{code}}))',
- 'ERROR:', 'WARNING:', ':{{number}}', True)
+CodeEngine('julia', 'julia', '.jl', '{julia} "{file}.jl"', julia_template,
+ julia_wrapper, 'println(jltex.formatter({code}))',
+ 'ERROR:', 'WARNING:', ':{number}', True)
SubCodeEngine('julia', 'jl')
+
+octave_template = '''
+ # Octave only supports @CLASS, not classdef
+ # So use a struct plus functions as a substitute for a utilities class
+
+ global octavetex = struct();
+ octavetex.docdir = pwd();
+ try
+ cd '{Workingdir}';
+ catch
+ arg_list = argv()
+ if size(arg_list, 1) == 1 && arg_list{{1}} == '--manual'
+ else
+ error("Could not find directory {workingdir}");
+ end
+ end
+ if find_dir_in_path(octavetex.docdir)
+ else
+ addpath(octavetex.docdir);
+ end
+
+ {extend}
+
+ octavetex.dependencies = {{}};
+ octavetex.created = {{}};
+ octavetex._context_raw = '';
+
+ function octavetex_formatter(argin)
+ disp(argin);
+ end
+ octavetex.formatter = @(argin) octavetex_formatter(argin);
+
+ function octavetex_before()
+ end
+ octavetex.before = @() octavetex_before();
+
+ function octavetex_after()
+ end
+ octavetex.after = @() octavetex_after();
+
+ function octavetex_add_dependencies(varargin)
+ global octavetex;
+ for i = 1:length(varargin)
+ octavetex.dependencies{{end+1}} = varargin{{i}};
+ end
+ end
+ octavetex.add_dependencies = @(varargin) octavetex_add_dependencies(varargin{{:}});
+
+ function octavetex_add_created(varargin)
+ global octavetex;
+ for i = 1:length(varargin)
+ octavetex.created{{end+1}} = varargin{{i}};
+ end
+ end
+ octavetex.add_created = @(varargin) octavetex_add_created(varargin{{:}});
+
+ function octavetex_set_context(argin)
+ global octavetex;
+ if ~strcmp(argin, octavetex._context_raw)
+ octavetex._context_raw = argin;
+ hash = struct;
+ argin_kv = strsplit(argin, ',');
+ for i = 1:length(argin_kv)
+ kv = strsplit(argin_kv{{i}}, '=');
+ k = strtrim(kv{{1}});
+ v = strtrim(kv{{2}});
+ hash = setfield(hash, k, v);
+ end
+ octavetex.context = hash;
+ end
+ end
+ octavetex.set_context = @(argin) octavetex_set_context(argin);
+
+ function out = octavetex_pt_to_in(argin)
+ if ischar(argin)
+ if length(argin) > 2 && argin(end-1:end) == 'pt'
+ out = str2num(argin(1:end-2))/72.27;
+ else
+ out = str2num(argin)/72.27;
+ end
+ else
+ out = argin/72.27;
+ end
+ end
+ octavetex.pt_to_in = @(argin) octavetex_pt_to_in(argin);
+
+ function out = octavetex_pt_to_cm(argin)
+ out = octavetex_pt_to_in(argin)*2.54;
+ end
+ octavetex.pt_to_cm = @(argin) octavetex_pt_to_cm(argin);
+
+ function out = octavetex_pt_to_mm(argin)
+ out = octavetex_pt_to_in(argin)*25.4;
+ end
+ octavetex.pt_to_mm = @(argin) octavetex_pt_to_mm(argin);
+
+ function out = octavetex_pt_to_bp(argin)
+ out = octavetex_pt_to_in(argin)*72;
+ end
+ octavetex.pt_to_bp = @(argin) octavetex_pt_to_bp(argin);
+
+ function octavetex_cleanup()
+ global octavetex;
+ fprintf(strcat('{dependencies_delim}', "\\n"));
+ for i = 1:length(octavetex.dependencies)
+ fprintf(strcat(octavetex.dependencies{{i}}, "\\n"));
+ end
+ fprintf(strcat('{created_delim}', "\\n"));
+ for i = 1:length(octavetex.created)
+ fprintf(strcat(octavetex.created{{i}}, "\\n"));
+ end
+ end
+ octavetex.cleanup = @() octavetex_cleanup();
+
+ octavetex.id = '{family}_{session}_{restart}';
+ octavetex.family = '{family}';
+ octavetex.session = '{session}';
+ octavetex.restart = '{restart}';
+
+ {body}
+
+ octavetex.cleanup()
+ '''
+
+octave_wrapper = '''
+ octavetex.command = '{command}';
+ octavetex.set_context('{context}');
+ octavetex.args = '{args}';
+ octavetex.instance = '{instance}';
+ octavetex.line = '{line}';
+
+ octavetex.before()
+
+ fprintf(strcat('{stdoutdelim}', "\\n"));
+ fprintf(stderr, strcat('{stderrdelim}', "\\n"));
+ {code}
+
+ octavetex.after()
+ '''
+
+CodeEngine('octave', 'octave', '.m',
+ '{octave} -q "{File}.m"',
+ octave_template, octave_wrapper, 'disp({code})',
+ 'error', 'warning', 'line {number}')
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_install.py b/Master/texmf-dist/scripts/pythontex/pythontex_install.py
new file mode 100755
index 00000000000..64ac0e4d1b8
--- /dev/null
+++ b/Master/texmf-dist/scripts/pythontex/pythontex_install.py
@@ -0,0 +1,494 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+'''
+Install PythonTeX
+
+This installation script is written to work with TeX Live and MiKTeX. Note
+that PythonTeX is included in TeX Live 2013 and later, and may be installed
+via the package manager. Thus, this installation script is only needed with
+TeX Live when you wish to install the latest version. PythonTeX is not
+currently available via the MiKTeX package manager.
+
+The script will automatically overwrite (and thus update) all previously
+installed PythonTeX files in the designated installation location. When
+Kpathsea is available, files may be installed in TEXMFDIST, TEXMFLOCAL,
+TEXMFHOME, or a manually specified location. Otherwise, the installation
+location must be specified manually. Installing in TEXMFDIST is useful
+under TeX Live if you want to install PythonTeX and then update it in the
+future via the package manager.
+
+The `mktexlsr` (TeX Live) or `initexmf --update-fndb` (MiKTeX) command is
+executed at the end of the script, to make the system aware of any new files.
+
+Under TeX Live, 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`. Under MiKTeX, it attempts to create
+a batch file in `miktex/bin`.
+
+
+Copyright (c) 2012-2014, 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 notices
+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))
+
+
+# Attempt to detect the TeX distribution
+try:
+ if sys.version_info.major == 2:
+ texout = check_output(['latex', '--version'])
+ else:
+ texout = check_output(['latex', '--version']).decode('utf-8')
+except:
+ sys.exit('Could not retrieve latex info when running "latex --version"')
+if 'TeX Live' in texout:
+ detected_texdist = True
+ texlive = True
+ miktex = False
+elif platform.system() == 'Windows' and 'MiKTeX' in texout:
+ detected_texdist = True
+ texlive = False
+ miktex = True
+else:
+ detected_texdist = False
+ texlive = False
+ miktex = False
+
+
+# Make sure all necessary files are present
+# The pythontex_gallery and pythontex_quickstart are optional; we
+# check for them when installing doc, and install if available
+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',
+ 'syncpdb.py']
+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:
+ sys.exit('Exiting due to missing files.')
+
+
+# Retrieve the location of valid TeX trees
+if sys.version_info[0] == 2:
+ try:
+ texmf_dist = check_output(['kpsewhich', '-var-value', 'TEXMFDIST']).rstrip('\r\n')
+ except:
+ texmf_dist = None
+ try:
+ texmf_local = check_output(['kpsewhich', '-var-value', 'TEXMFLOCAL']).rstrip('\r\n')
+ except:
+ texmf_local = None
+ try:
+ texmf_home = check_output(['kpsewhich', '-var-value', 'TEXMFHOME']).rstrip('\r\n')
+ except:
+ texmf_home = None
+else:
+ try:
+ texmf_dist = check_output(['kpsewhich', '-var-value', 'TEXMFDIST']).decode('utf-8').rstrip('\r\n')
+ except:
+ texmf_dist = None
+ try:
+ texmf_local = check_output(['kpsewhich', '-var-value', 'TEXMFLOCAL']).decode('utf-8').rstrip('\r\n')
+ except:
+ texmf_local = None
+ try:
+ texmf_home = check_output(['kpsewhich', '-var-value', 'TEXMFHOME']).decode('utf-8').rstrip('\r\n')
+ except:
+ texmf_home = None
+
+
+# Get installation location from user
+texmf_vars = [texmf_dist, texmf_local, texmf_home]
+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
+ (assuming that is supported).
+
+ 1. TEXMFDIST
+ {0}
+ 2. TEXMFLOCAL
+ {1}
+ 3. TEXMFHOME
+ {2}
+ 4. Manual location
+
+ 5. Exit without installing
+ '''.format(*[x if x else '<INVALID>' for x in texmf_vars])
+
+if any(texmf_vars):
+ path_choice = ''
+ while (path_choice not in ('1', '2', '3', '4', '5') or
+ (int(path_choice) <= 3 and not texmf_vars[int(path_choice)-1])):
+ print(textwrap.dedent(message))
+ path_choice = input('Installation location (number): ')
+ if path_choice == '':
+ sys.exit()
+ if path_choice == '1':
+ texmf_path = texmf_dist
+ elif path_choice == '2':
+ texmf_path = texmf_local
+ elif path_choice == '3':
+ texmf_path = texmf_home
+ elif path_choice == '4':
+ texmf_path = input('Enter a path:\n')
+ if texmf_path == '':
+ sys.exit()
+ if platform.system() == 'Windows':
+ if 'texlive' in texmf_path.lower():
+ detected_texdist = True
+ texlive = True
+ miktex = False
+ elif 'miktex' in texmf_path.lower():
+ detected_texdist = True
+ texlive = False
+ miktex = True
+ else:
+ sys.exit()
+else:
+ print('Failed to detect possible installation locations automatically.')
+ print('TEXMF paths could not be located with kpsewhich.')
+ texmf_path = input('Plese enter an installation path, or press "Enter" to exit:\n')
+ if texmf_path == '':
+ 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 should only be needed for manual input, but it's a good check
+if not path.isdir(texmf_path):
+ sys.exit('Invalid installation path. Exiting.')
+
+# 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')
+# May need to create some local directories
+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 = ''
+ while choice not in ('y', 'n'):
+ choice = input('Some directories do not exist. Create them? [y/n] ')
+ if choice == '':
+ sys.exit()
+ if choice == 'y':
+ make_paths = True
+ try:
+ makedirs(eachpath)
+ print(' * Created ' + eachpath)
+ except (OSError, IOError) as e:
+ if e.errno == 13:
+ print('\nInsufficient permission to install PythonTeX')
+ if platform.system() == 'Windows':
+ message = '''
+ You may need to run the installer as "administrator".
+ This may be done under Vista and later by right-clicking on
+ pythontex_install.bat, then selecting "Run as administrator".
+ Or you can open a command prompt as administrator
+ (Start, Programs, Accessories, right-click Command Prompt,
+ Run as administrator), change to the directory in which
+ pythontex_install.py is located, and run
+ "python pythontex_install.py".
+ '''
+ print(textwrap.dedent(message))
+ call(['pause'], shell=True)
+ else:
+ print('(For example, you may need "sudo", or possibly "sudo env PATH=$PATH")\n')
+ sys.exit(1)
+ else:
+ raise
+ 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)
+ copy('syncpdb.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, IOError) as e:
+ if e.errno == 13:
+ print('\nInsufficient permission to install PythonTeX')
+ if platform.system() == 'Windows':
+ message = '''
+ You may need to run the installer as "administrator".
+ This may be done under Vista and later by right-clicking on
+ pythontex_install.bat, then selecting "Run as administrator".
+ Or you can open a command prompt as administrator
+ (Start, Programs, Accessories, right-click Command Prompt,
+ Run as administrator), change to the directory in which
+ pythontex_install.py is located, and run
+ "python pythontex_install.py".
+ '''
+ print(textwrap.dedent(message))
+ call(['pause'], shell=True)
+ else:
+ 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
+ # or a batch file if under MiKTeX. Otherwise, alert the user
+ # regarding the need for a wrapper or batch file.
+ if miktex:
+ try:
+ if sys.version_info.major == 2:
+ bin_path = check_output(['kpsewhich', '-var-value', 'TEXMFDIST']).rstrip('\r\n')
+ else:
+ bin_path = check_output(['kpsewhich', '-var-value', 'TEXMFDIST']).decode('utf-8').rstrip('\r\n')
+ bin_path = path.join(bin_path, 'miktex', 'bin')
+
+ for s in ('pythontex.py', 'depythontex.py'):
+ batch = '@echo off\n"{0}" %*\n'.format(path.join(scripts_path, s))
+ f = open(path.join(bin_path, s.replace('.py', '.bat')), 'w')
+ f.write(batch)
+ f.close()
+ except:
+ message = '''
+ Could not create a batch file for launching pythontex.py and
+ depythontex.py. You will need to create a batch file manually.
+ Sample batch files are included with the main PythonTeX files.
+ The batch files 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:
+ # 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')):
+ for f in ('pythontex.py', 'depythontex.py'):
+ copy(path.join(bin_path, 'runscript.exe'), path.join(bin_path, '{0}.exe'.format(f.rsplit('.')[0])))
+ 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):
+ # Unlink any old symlinks if they exist, and create new ones
+ # Not doing this gave permissions errors under Ubuntu
+ for f in ('pythontex.py', 'pythontex2.py', 'pythontex3.py',
+ 'depythontex.py', 'depythontex2.py', 'depythontex3.py'):
+ link = path.join(bin_path, f)
+ if path.exists(link):
+ unlink(link)
+ symlink(path.join(scripts_path, f), 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
+ for f in ('pythontex.py', 'pythontex2.py', 'pythontex3.py',
+ 'depythontex.py', 'depythontex2.py', 'depythontex3.py'):
+ link = path.join(bin_path, f)
+ if path.exists(link):
+ unlink(link)
+ symlink(path.join(scripts_path, f), 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
+if not miktex:
+ try:
+ # Need to adjust if under Windows with a user-specified TeX Live
+ # installation and a default MiKTeX installation; want to call
+ # mktexlsr for the user-specified TeX Live installation
+ if platform.system() == 'Windows' and 'MiKTeX' in texout:
+ check_call(path.join(bin_path, 'mktexlsr'))
+ else:
+ check_call(['mktexlsr'])
+ print('\nRunning "mktexlsr" to make TeX aware of new files...')
+ except:
+ print('Could not run "mktexlsr".')
+ print('Your system may not be aware of newly installed files.')
+else:
+ success = False
+ try:
+ check_call(['initexmf', '--admin', '--update-fndb'])
+ print('\nRunning "initexmf --admin --update-fndb" to make TeX aware of new files...')
+ check_call(['initexmf', '--update-fndb'])
+ print('\nRunning "initexmf --update-fndb" to make TeX aware of new files...')
+ success = True
+ except:
+ pass
+ if not success:
+ try:
+ check_call(['initexmf', '--update-fndb'])
+ print('\nRunning "initexmf --update-fndb" to make TeX aware of new files...')
+ print('Depending on your installation settings, you may also need to run')
+ print('"initexmf --admin --update-fndb"')
+ except:
+ print('Could not run "initexmf --update-fndb" or "initexmf --admin --update-fndb"')
+ 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_install_texlive.py b/Master/texmf-dist/scripts/pythontex/pythontex_install_texlive.py
deleted file mode 100755
index 28176e8b704..00000000000
--- a/Master/texmf-dist/scripts/pythontex/pythontex_install_texlive.py
+++ /dev/null
@@ -1,343 +0,0 @@
-#!/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
index 75d3115a395..2731e7ab765 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex_utils.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex_utils.py
@@ -6,7 +6,7 @@ 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
+Copyright (c) 2012-2014, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
@@ -17,6 +17,8 @@ Licensed under the BSD 3-Clause License:
# Imports
import sys
import warnings
+if sys.version_info.major == 2:
+ import io
# Most imports are only needed for SymPy; these are brought in via
# "lazy import." Importing unicode_literals here shouldn't ever be necessary
@@ -40,14 +42,14 @@ class PythonTeXUtils(object):
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
+ * family
+ * session
+ * restart
+ * command
+ * context
+ * args
+ * instance
+ * line
Future file handle for output that is saved via macros
* macrofile
@@ -62,6 +64,63 @@ class PythonTeXUtils(object):
'''
self.set_formatter(fmtr)
+ # We need a function that will process the raw `context` into a
+ # dictionary with attributes
+ _context_raw = None
+ class _DictWithAttr(dict):
+ pass
+ def set_context(self, expr):
+ '''
+ Convert the string `{context}` into a dict with attributes
+ '''
+ if not expr or expr == self._context_raw:
+ pass
+ else:
+ self._context_raw = expr
+ self.context = self._DictWithAttr()
+ k_and_v = [map(lambda x: x.strip(), kv.split('=')) for kv in expr.split(',')]
+ for k, v in k_and_v:
+ if v.startswith('!!int '):
+ v = int(float(v[6:]))
+ elif v.startswith('!!float '):
+ v = float(v[8:])
+ elif v.startswith('!!str '):
+ v = v[6:]
+ self.context[k] = v
+ setattr(self.context, k, v)
+
+ # A primary use for contextual information is to pass dimensions from the
+ # TeX side to the Python side. To make that as convenient as possible,
+ # we need some length conversion functions.
+ # Conversion reference: http://tex.stackexchange.com/questions/41370/what-are-the-possible-dimensions-sizes-units-latex-understands
+ def pt_to_in(self, expr):
+ '''
+ Convert points to inches. Accepts numbers, strings of digits, and
+ strings of digits that end with `pt`.
+ '''
+ try:
+ ans = expr/72.27
+ except:
+ if expr.endswith('pt'):
+ expr = expr[:-2]
+ ans = float(expr)/72.27
+ return ans
+ def pt_to_cm(self, expr):
+ '''
+ Convert points to centimeters.
+ '''
+ return self.pt_to_in(expr)*2.54
+ def pt_to_mm(self, expr):
+ '''
+ Convert points to millimeters.
+ '''
+ return self.pt_to_in(expr)*25.4
+ def pt_to_bp(self, expr):
+ '''
+ Convert points to big (DTP or PostScript) points.
+ '''
+ return self.pt_to_in(expr)*72
+
# We need a context-aware interface to SymPy's latex printer. The
# appearance of typeset math should depend on where it appears in a
@@ -366,6 +425,21 @@ class PythonTeXUtils(object):
for creation in self._created:
print(creation)
+ # A custom version of `open()` is useful for automatically tracking files
+ # opened for reading as dependencies and tracking files opened for
+ # writing as created files.
+ def open(self, name, mode='r', *args, **kwargs):
+ if mode in ('r', 'rt', 'rb'):
+ self.add_dependencies(name)
+ elif mode in ('w', 'wt', 'wb'):
+ self.add_created(name)
+ else:
+ warnings.warn('Unsupported mode {0} for file tracking'.format(mode))
+ if sys.version_info.major == 2 and (len(args) > 1 or 'encoding' in kwargs):
+ return io.open(name, mode, *args, **kwargs)
+ else:
+ return open(name, mode, *args, **kwargs)
+
def cleanup(self):
self._save_dependencies()
self._save_created()
diff --git a/Master/texmf-dist/source/latex/pythontex/depythontex.bat b/Master/texmf-dist/source/latex/pythontex/depythontex.bat
new file mode 100755
index 00000000000..77fe1ec6366
--- /dev/null
+++ b/Master/texmf-dist/source/latex/pythontex/depythontex.bat
@@ -0,0 +1,2 @@
+@echo off
+depythontex.py %*
diff --git a/Master/texmf-dist/source/latex/pythontex/pythontex.bat b/Master/texmf-dist/source/latex/pythontex/pythontex.bat
index 0dfa442b4f7..f200d2e9f6b 100755
--- a/Master/texmf-dist/source/latex/pythontex/pythontex.bat
+++ b/Master/texmf-dist/source/latex/pythontex/pythontex.bat
@@ -1,2 +1,2 @@
-@echo off
-pythontex.py %*
+@echo off
+pythontex.py %*
diff --git a/Master/texmf-dist/source/latex/pythontex/pythontex.dtx b/Master/texmf-dist/source/latex/pythontex/pythontex.dtx
index a7da1010aeb..cdba66cae41 100644
--- a/Master/texmf-dist/source/latex/pythontex/pythontex.dtx
+++ b/Master/texmf-dist/source/latex/pythontex/pythontex.dtx
@@ -26,7 +26,7 @@
%<package>\NeedsTeXFormat{LaTeX2e}[1999/12/01]
%<package>\ProvidesPackage{pythontex}
%<*package>
- [2013/08/26 Version~0.12]
+ [2014/07/14 Version~0.13 ]
%</package>
%
%<*driver>
@@ -34,6 +34,7 @@
\makeatletter
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
+\usepackage{lmodern}
\usepackage{textcomp}
\usepackage[svgnames]{xcolor}
\usepackage{environ}
@@ -45,16 +46,14 @@
%\newcommand{\pytxtodo}[1]{\textcolor{red}{TO~DO: \scantokens{#1}}}
\newcommand{\changestext}{}
\NewEnviron{changelog}[2]{%
- \g@addto@macro\changestext{\item[#1] (#2) \begin{itemize}}%
+ \g@addto@macro\changestext{\subsection*{\textbf{#1} (#2)}}%
\expandafter\g@addto@macro\expandafter\changestext\expandafter{\BODY}%
- \g@addto@macro\changestext{\end{itemize}}%
+ \g@addto@macro\changestext{}%
}
\newcommand{\PrintChangelog}{%
\addcontentsline{toc}{section}{Version History}
\section*{Version History}%
- \begin{description}%
\changestext
- \end{description}%
}
\EnableCrossrefs
\CodelineIndex
@@ -68,7 +67,7 @@
%</driver>
% \fi
%
-% \CheckSum{2928}
+% \CheckSum{3010}
%
% \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
@@ -87,8 +86,61 @@
% Right brace \} Tilde \~}
%
%
+% \begin{changelog}{v0.13}{2014/07/14}
+% \textbf{New features}
+% \begin{itemize}
+% \item Added \texttt{-{}-interactive} command-line option. This runs a single session in interactive mode, allowing user input. Among other things, this is useful when working with debuggers.
+% \item Added \texttt{-{}-debug} command-line option. This runs a single session with the default debugger in interactive mode. Currently, only standard (non-console) Python sessions are supported. The default Python debugger is the new \texttt{syncpdb}, which wraps \texttt{pdb} and synchronizes code line numbers with document line numbers. All \texttt{pdb} commands that take a line number or filename:lineno as an argument will refer to document files and line numbers when the argument has a percent symbol (\texttt{\%}) as a prefix. For example, \texttt{list \%50} lists code that came from around line 50 in the document. The \texttt{-{}-debug} option will support other languages and provide for customization in the future.
+% \item Added command-line option \texttt{-{}-jobs}, which allows the maximum number of concurrent processes to be specified (\#35).
+% \item Added support for GNU Octave, via the \texttt{octave} family of commands and environments (\#36). Parsing of Octave stderr is not ideal, though synchronization works in most cases; this will be addressed by a future rewrite of the stderr parser.
+% \item Installer now automatically works with MiKTeX, not just TeX Live.
+% \item The PythonTeX utilities class has a new \texttt{open()} method that opens files and automatically tracks dependencies/created files.
+% \item When \texttt{pythontex2.py} and \texttt{pythontex3.py} are run directly, the Python interpreter is automatically set to a reasonable default (\texttt{py -2} or \texttt{py -3} under Windows, using the Python 3.3+ wrapper; \texttt{python2} or \texttt{python3} under other systems).
+% \item The installer now creates symlinks for the numbered scripts \texttt{pythontex*.py} and \texttt{depythontex*.py}.
+% \item Added Python version checking to all numbered scripts.
+% \item Under Python, the type of data passed via \texttt{\string\setpythontexcontext} may now be set using YAML-style tags (\texttt{!!str}, \texttt{!!int}, \texttt{!!float}). For example, \texttt{\string\setpythontexcontext\{myint=!!int 123\}}.
+% \item The \texttt{fancyvrb} options \texttt{firstline} and \texttt{lastline} now work with the \texttt{pygments} environment and \texttt{\string\inputpygments} command. This required some additional patching of \texttt{fancyvrb}.
+% \item The \texttt{pytx@Verbatim} and \texttt{pytx@SaveVerbatim} environments are now used for typesetting verbatim code. These are copies of the \texttt{fancyvrb} environments. This prevents conflicts when literal \texttt{Verbatim} and \texttt{SaveVerbatim} environments need to be typeset.
+% \item Improved \texttt{latexmk} compatibility (\#40). Added discussion of \texttt{latexmk} usage to documentation.
+% \item Tildes \texttt{\string~} may now be used in \texttt{outputdir} and \texttt{workingdir} to refer to the user's home directory, even under Windows.
+% \end{itemize}
+% \textbf{Bugfixes}
+% \begin{itemize}
+% \item Fixed a bug that prevented created files from being cleaned up when the working directory was not the document root directory and the full path to the files was not provided.
+% \item Fixed a bug that prevented the \texttt{fvextfile} option from working when external files were highlighted.
+% \end{itemize}
+% \end{changelog}
+%
+% \begin{changelog}{v0.13-beta}{2014/02/06}
+% \textbf{New features}
+% \begin{itemize}
+% \item Switching to GitHub's Releases for downloads.
+% \item TeX information such as page dimensions may now be easily passed to the programming-language side, using the new \texttt{\string\setpythontexcontext} command. Contextual information is stored in the \texttt{context} attribute of the utilities class, which is a dictionary (and also has attributes in Python).
+% \item The utilities class now has \texttt{pt\_to\_in()}, \texttt{pt\_to\_cm()}, and \texttt{pt\_to\_mm()} methods for converting units of TeX points into inches, centimeters, and millimeters. These work with integers and floats, as well as strings that consist of numbers and optionally end in ``pt''. There is also a \texttt{pt\_to\_bp()} for converting TeX points (1/72.27 inch) into big (DTP or PostScript) points (1/72 inch).
+% \item Expanded Quickstart. Quickstart is now compatible with all LaTeX engines. Quickstart now avoids \texttt{microtype} issues on some systems (\#32).
+% \item Added information on citing PythonTeX (\#28).
+% \item Utilities class has a new attribute \texttt{id}, which is a string that joins the command family name, session name, and session restart parameters with underscores. This may be used in creating files that need a name that contains a unique, session-based identifier (for example, names for figures that are saved automatically).
+% \end{itemize}
+% \textbf{Backward-incompatible changes}
+% \begin{itemize}
+% \item All utilities-class attributes with names of the form \texttt{input\_*} have been renamed with the ``\texttt{input\_}'' removed. Among other things, this makes it easier to access the \texttt{context} attribute (\texttt{pytex.context} vs.\ \texttt{pytex.input\_context}).
+% \item \texttt{depythontex} now has \texttt{-o} and \texttt{-{}-output} command-line options for specifying the name of the output file. If an output file is not specified, then output is written to \texttt{stdout}. This allows \texttt{depythontex} output to be piped to another program.
+% \item All scripts \texttt{*2.py} now have shebangs with \texttt{env python2}, and all scripts \texttt{*3.py} now have shebangs with \texttt{env python3}. This allows the wrapper scripts (\texttt{env python} shebang) to be used with the default Python installation, and the numbered scripts to be used with specific versions. Remember that except for console content, the \texttt{-{}-interpreter} option is what determines the Python version that actually executes code. The version of Python used to launch \texttt{pythontex.py} merely determines the version that manages code execution. (\texttt{-{}-interpreter} support for console content is coming.)
+% \item Changed the template style used in the \texttt{CodeEngine} class. Replacement fields are now surrounded by single curly braces (as in Python's format string syntax), rather than double curly braces. Literal curly braces are obtained by doubling braces. This allows the use of literal adjacent double braces in templates, which was not possible previously.
+% \item The Julia template now uses the new \texttt{in()} function, replacing \texttt{contains()}. This requires Julia v0.2.0+.
+% \end{itemize}
+% \textbf{Bugfixes}
+% \begin{itemize}
+% \item Modified test for LuaTeX, so that \texttt{\string\directlua} is not \texttt{\string\let} to \texttt{\string\relax} if it does not exist. This was causing incompatibility with \texttt{babel} under pdfTeX and XeTeX (\#33).
+% \item Added missing shebangs to \texttt{depythontex*.py}. Handling of \texttt{utilspath} is now more forgiving, so that \texttt{pythontex\_utils.py} can be installed in alternate locations (\#23).
+% \item \texttt{depythontex} no longer leaves a blank line where \texttt{\string\usepackage\{pythontex\}} was removed.
+% \item Console environments typeset with \texttt{fancyvrb} no longer end with an unnecessary empty line.
+% \item Fixed bug in installer when \texttt{kpsewhich} was not found (\#21).
+% \end{itemize}
+% \end{changelog}
%
% \begin{changelog}{v0.12}{2013/08/26}
+% \begin{itemize}
% \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.
@@ -101,9 +153,11 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.12beta}{2013/06/24}
+% \begin{itemize}
% \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.
@@ -136,9 +190,11 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.11}{2013/04/21}
+% \begin{itemize}
% \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.
@@ -159,9 +215,11 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.11beta}{2013/02/17}
+% \begin{itemize}
% \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.
@@ -170,9 +228,11 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.10beta2}{2013/01/23}
+% \begin{itemize}
% \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}.
@@ -181,9 +241,11 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.10beta}{2013/01/09}
+% \begin{itemize}
% \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.
@@ -212,9 +274,11 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.9beta3}{2012/07/17}
+% \begin{itemize}
% \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.
@@ -224,14 +288,19 @@
% \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{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.9beta2}{2012/05/09}
+% \begin{itemize}
% \item Changed Python output extension to .stdout.
+% \end{itemize}
% \end{changelog}
%
% \begin{changelog}{v0.9beta}{2012/04/27}
+% \begin{itemize}
% \item Initial public beta release.
+% \end{itemize}
% \end{changelog}
%
%
@@ -258,7 +327,7 @@
%
% 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.
+% 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 already have built-in support: Ruby and Julia.
% \end{abstract}
%
%
@@ -310,13 +379,21 @@
% 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{Citing \pytex}
+% \label{sec:citing}
+%
+% If you use \pytex\ in your writing and research, please consider citing it in any resulting publications. Currently, the best paper to cite is the one published in the 2013 SciPy proceedings. Several additional papers about \pytex\ are now under review or in preparation; this list will be edited when they are published.
+% \begin{itemize}
+% \item ``Reproducible Documents with PythonTeX,'' Geoffrey M.\ Poore. \textit{Proceedings of the 12th Python in Science Conference} (2013), pp.\ 73--79. Full text and Bib\TeX\ entry available at \url{http://conference.scipy.org/proceedings/scipy2013/poore.html}.
+% \end{itemize}
+%
%
% \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\ 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| (part of the |caption| bundle), |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}.
%
@@ -330,18 +407,23 @@
% \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 Synchronized Python Debugger |syncpdb.py|
% \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 installation script |pythontex_install.py| for \TeX\ Live and MiKTeX
% \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:
+% 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 and MiKTeX. 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{For a MiKTeX installation, you may need administrator privileges; running \texttt{pythontex\_install.bat} as administrator may be simplest.}
+%
+% \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 versions 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
+% sudo env PATH=$PATH python pythontex_install.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.
@@ -361,6 +443,7 @@
% \item |pythontex_engines.py|
% \item |pythontex_utils.py|
% \item |depythontex.py|, |depythontex2.py| and |depythontex3.py|
+% \item |syncpdb.py|
% \end{itemize}
% \item \meta{\TeX\ tree root}|/source/latex/pythontex/|
% \begin{itemize}
@@ -376,6 +459,7 @@
%
%
% \subsection{Compiling documents using \pytex}
+% \label{sec:installing-and-running:compiling}
%
% 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.
%
@@ -405,6 +489,8 @@
% 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 |--jobs| This sets the maximum number of concurrent processes. By default, this will be Python's |multiprocessing.cpu_count()|, which is the number of CPUs in the system. It may be useful to set a smaller value when some jobs are particularly resource intensive or themselves use subprocesses.
+%
% \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
@@ -414,6 +500,33 @@
% 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).
+%
+% \item |--interactive [<family>:<session>:<restart>]| This is used to run a single session in interactive mode. This allows user input. Code output is written to stdout. Interactive mode is particularly useful when working with debuggers (but also see the |--debug| option).
+%
+% |[<family>:<session>:<restart>]| is optional; if it is not provided, the default session is executed. For non-default sessions (or if there are multiple default sessions, due to the use of multiple families of commands), simply supplying the session name is usually sufficient (for example, |--debug session|). The full combination of |[<family>:<session>:<restart>]| (for example, |py:session:default|) is only necessary when the session name alone would be ambiguous.
+%
+% Note that when a session is run in interactive mode, it will \emph{not} save printed content in a form that may be brought back into the document. You will have to run the session again in normal mode to complete document compilation.
+%
+% Code that requires user input will cause \pytex\ to ``hang'' when \pytex\ is not running in interactive mode. This is because the code will request user input, but no input is possible given the way that the code is being executed, so the code will wait for input forever. It is inefficient constantly to add and then delete interactive code as you switch between normal and interactive modes. To avoid this, you can conditionally invoke code that requires input. In interactive mode, the temporary script that is executed is given the command-line argument |--interactive|. You can check for the presence of this argument, and only invoke interactive code if it is present. For example, under Python you could start the |pdb| debugger, only when the code is being executed in interactive mode, using commands such as the following.
+%\begin{verbatim}
+%import pdb
+%import sys
+%if '--interactive' in sys.argv[1:]:
+% pdb.set_trace()
+%\end{verbatim}
+%
+% This option is currently not compatible with Python console commands and environments.
+%
+% \item |--debug [<family>:<session>:<restart>]| This is used to run a single session with the default debugger in interactive mode. Currently, only standard Python sessions are supported. (Python console commands and environments are not supported.) Support for other languages and support for customization will be added in the future.
+%
+% |[<family>:<session>:<restart>]| is optional; if it is not provided, the default session is executed. For non-default sessions (or if there are multiple default sessions, due to the use of multiple families of commands), simply supplying the session name is usually sufficient (for example, |--debug session|). The full combination of |[<family>:<session>:<restart>]| (for example, |py:session:default|) is only necessary when the session name alone would be ambiguous.
+%
+% Note that when a session is run in debug mode, it will \emph{not} save printed content in a form that may be brought back into the document. You will have to run the session again in normal mode to complete document compilation.
+%
+% The default Python debugger is |syncpdb|, the Synchronized Python Debugger. It provides a wrapper around |pdb| that is aware of the connection between the code and the \LaTeX\ document from which it was extracted. All |pdb| commands function normally. In addition, commands that take a line number or filename:lineno as an argument will also take these same values with a percent symbol |%| prefix. If the percent symbol is present, then |syncpdb| interprets the filename and line number as referring to the document, rather than to the code that is executed. It will translate the filename and line number to the corresponding code equivalents, and then pass these to the standard |pdb| internals. For example, the |pdb| command |list 50| would list the code that is being executed, centered around line 50. |syncpdb| allows the command |list %10|, which would list the code that is being executed, centered around the code that came from line 10 in the main \LaTeX\ document. (If no file name is given, then the main \LaTeX\ document is assumed.) If the code instead came from an inputed file |input.tex|, then |list %input.tex:10| could be used. Further details are provided at \href{https://github.com/gpoore/syncpdb}{github.com/gpoore/syncpdb}.
+%
+% The temporary script that is executed is given the command-line argument |--interactive| when run in debug mode. You can check for the presence of this argument if you wish to invoke code that requires user input conditionally. See the |--interactive| command-line option for more details.
+%
% \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.
@@ -437,7 +550,7 @@
%
% \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.
+% 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|, the Julia families |jl| and |julia|, and the Octave family |octave| may be created.
%
%
% \DescribeMacro{gobble=none/auto default:none}
@@ -758,6 +871,8 @@
%
% 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 has an attribute |context|. This is a dictionary that can contain contextual information, such as page dimensions, from the \TeX\ side. Values may also be accessed as attributes rather than as dictionary keys. To determine what contextual information is available, and for additional details, see |\setpythontexcontext| under Section~\ref{sec:usage:advanced-usage}. For working with contextual data, the utilities class provides |pt_to_in()|, |pt_to_cm()|, |pt_to_mm()|, and |pt_to_bp()| methods for converting from \TeX\ points to other units.
+%
% 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.)
@@ -768,7 +883,7 @@
% 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 \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| within a |pycode| or |pythontexcustomcode| environment. Any subsequent uses of |\py| will then use |my_func()| to perform formatting.
%
% The utilities class also provides methods for tracking dependencies and created files.
% \begin{itemize}
@@ -776,7 +891,7 @@
%
% \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.
+% 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 indicate 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.
%
@@ -785,20 +900,28 @@
% 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.
+% 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 this reason, the \pytex\ utilities class provides an |open()| method that automatically tracks dependencies and created files.
+% \begin{itemize}
+% \item |pytex.open(|\meta{file}|, |\meta{mode}|, |\meta{args}|, | \meta{kwargs}|)| This method automatically tracks all files opened for reading (text or binary mode) as dependencies. It automatically tracks all files opened for writing (text or binary mode) as created files. Files opened for updating and appending will raise a warning, since it is not necessarily obvious how these files should be treated. The general form of the custom |open()| function is shown below.
% \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
+%def track_open(name, mode='r', *args, **kwargs):
+% if mode in ('r', 'rt', 'rb'):
+% pytex.add_dependencies(name)
+% elif mode in ('w', 'wt', 'wb'):
+% pytex.add_created(name)
+% else:
+% warnings.warn('Unsupported mode {0} for file tracking'.format(mode))
+% return open(name, mode, *args, **kwargs)
% \end{verbatim}
-% Notice that this approach does not deal with files opened for appending or updating; such cases require more complex logic.
+% \textbf{Unicode note for Python 2:} By default, |pytex.open()| call the standard Python 2 |open()|. If more than 3 positional arguments are used, or if the |encoding| keyword is used, then \href{https://docs.python.org/2/library/io.html}{\texttt{io.open()}} will be called instead. So if you are working with Unicode, make sure to specify an encoding in |pytex.open()| so that |io.open()| will be used, or manually encode/decode everything.
+% \end{itemize}
%
% 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.
%
+% The |before()| and |after()| methods may be redefined in any code or block command or environment, using the techniques described below. Once they have been redefined, the new methods will be called for all subsequent commands and environments. When redefining these methods, it is important to realize that the order of redefinition may be important. For example, if the new |before()| and |after()| depend upon one another, then you should call the old |after()| (if it does anything), then redefine the methods, and finally call the new |before()|. This is necessary because |after()| will be called after the end of the command or environment in which redefinition takes place. If |after()| has been redefined so that it depends on the new |before()|, and the new |before()| has not yet been called, errors will likely result. Other methods of dealing with this scenario, involving disabling |before()| and |after()| for a given command or environment, are being considered as potential features for a future release.
+%
+% When redefining |before()| and |after()|, you may wish to have behavior that is command- or environment-specific. Information about the current command or environment is available in |pytex.command|. The string |i| corresponds to an inline command such as |\py|; |b|, to an inline block such as |\pyb|; |c|, to inline code such as |\pyc|. Similarly, |code| corresponds to a code environment and |block| to a block environment.
+%
% You may redefine |before()| and |after()| at the class level. For example,
% \begin{verbatim}
%def open(self):
@@ -823,8 +946,7 @@
% 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])
+%pytex.basename = '_'.join([pytex.family, pytex.session, pytex.restart])
%
%# Need to keep track of total number of figures in each session
%pytex.fignum = 0
@@ -1020,6 +1142,29 @@
%
%
% \subsection{Advanced \pytex\ usage}
+% \label{sec:usage:advanced-usage}
+%
+% \DescribeMacro{\setpythontexcontext\marg{key-value~pairs}}
+%
+% This macro is used for passing contextual information such as page dimensions from the \TeX\ side to the Python/other language side. \meta{key-value~pairs} is a set of comma-delimited key-value pairs. An evaluated version of \meta{key-value~pairs} is passed to the programming language, wrapped in quotation marks to become a string. Thus, \meta{key-value~pairs} should \emph{not} contain quotation marks, backslashes, or other characters that would prevent the evaluated contents from being the body of a normal, quoted string.
+%
+% As an example, the following would pass the values of |\textwidth| and |\textheight| to the Python side.
+%\begin{verbatim}
+%\setpythontexcontext{textwidth=\the\textwidth, textheight=\the\textheight}
+%\end{verbatim}
+% Python would receive a string something like |"textwidth=390pt, textheight=592pt"|. This string would be parsed into key-value pairs, and the results stored in the |pytex.context| dictionary. For Python, the keys also become the names of attributes of |pytex.context|. Thus, the values may be accessed on the Python side via |pytex.context['textwidth']|, |pytex.context.textwidth|, etc.
+%
+% All contextual data is available as strings on the Python/other language side. For convenience, the utilities class provides unit conversion methods for converting from \TeX\ points to inches, centimeters, millimeters, and big (DTP or PostScript) points. These methods take integers, floats, or strings that consist of digits (optionally ending in ``pt''), and return floats. For example, |pytex.pt_to_in()|, |pytex.pt_to_cm()|, |pytex.pt_to_mm()|, |pytex.pt_to_bp()|. Keep in mind that the units of \TeX\ points are \href{http://tex.stackexchange.com/questions/41370/what-are-the-possible-dimensions-sizes-units-latex-understands}{$1/72.27$} of an inch, \emph{not} $1/72$ of an inch (which is a bp).
+%
+% There is also a type system for Python that allows the types of \meta{values} to be specified. Any \meta{value} beginning with |!!int| will become an integer; with |!!float|, a float; with |!!str|, a string. This notation is borrowed from \href{http://yaml.org/}{YAML}. For example,
+%\begin{verbatim}
+%\setpythontexcontext{a=!!int 42, b=!!float 42, c=!!str 42}
+%\end{verbatim}
+% \textbf{This type system is still under development and is subject to change in the future.} Once the system stabilizes, it will be extended to non-Python languages. Comments on the type system are welcome.
+%
+% The context may only be set in the preamble.
+%
+% Technical note: Contextual data is cached, so the dictionary (and its attributes, if applicable) is only updated when contextual data changes. This largely eliminates any potential overhead from contextual data.
%
% \DescribeMacro{\restartpythontexsession\marg{counter~value(s)}}
%
@@ -1037,13 +1182,13 @@
%
% 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).
+% Any slashes in \meta{output~directory} should be forward slashes ``|/|'' (even under Windows). Tildes |~| may be used to refer to the user's home directory, including 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).
+% Any slashes in \meta{working~directory} should be forward slashes ``|/|'' (even under Windows). Tildes |~| may be used to refer to the user's home directory, including 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.
%
@@ -1052,6 +1197,28 @@
% It is also possible to change the working directory from within Python code, via |os.chdir()|.
%
%
+% \subsection{Working with other programs}
+%
+% \subsubsection{\texttt{latexmk}}
+%
+% \pytex\ is compatible with |latexmk|. If you are compiling in the same location as the document source (if you are \emph{not} using |-auxdir|, |-outdir|, or |$out_dir|, or alternatively |TEXINPUTS|), then adding a simple rule such as the following to your |.latexmkrc| should usually be sufficient.
+%\begin{verbatim}
+%add_cus_dep('pytxcode', 'tex', 0, 'pythontex');
+%sub pythontex { return system("pythontex \"$_[0]\""); }
+%\end{verbatim}
+% This tells |latexmk| that the document (|tex|) depends on the file of code extracted from the document (|pytxcode|).\footnote{This is a slightly atypical use, if not a ``misuse,'' of \texttt{add\_cus\_dep()}. In the standard usage, the first argument is the extension of a file that is used to create another file with the extension given in the second argument, via the rule named in the fourth argument. In this case, we just want to run the rule whenever files with the first extension are modified. The extension given in the second argument is irrelevant, so long as a file with the document name and that extension exists. Since the \texttt{tex} file itself will exist, its extension is a logical choice for the second argument.} Whenever the document is compiled, the file of code is updated. If |latexmk| detects that the code changed, then it will run \pytex. When \pytex\ runs, it will modify at least one file that is brought into the document. |latexmk| will detect this modification, and automatically recompile the document.
+%
+% If you are compiling to a different directory (using |-auxdir|, |-outdir|, or |$out_dir|, or alternatively |TEXINPUTS|), then this rule may fail due to the different directory configuration. In that case, you should use |\setpythontexoutputdir{.}| so that \pytex\ will store its output in the current default location, rather than in a subdirectory, to ensure that |latexmk| will locate the output files. Since the |tex| source is no longer in the location of the compiled output, you also need a different dependency specification. It is probably simplest to use the |pytxmcr| file that \pytex\ always generates.
+%\begin{verbatim}
+%add_cus_dep('pytxcode','pytxmcr',0,'pythontex');
+%sub pythontex { return system("pythontex.py \"$_[0]\""); }
+%\end{verbatim}
+% Note that this configuration should \emph{always} work, but has the disadvantage of requiring that \pytex\ not use a subdirectory to isolate the files it automatically generates.
+%
+% If you are using the |rerun=always| setting, the above rules will fail to run \pytex\ on each and every compile; in that situation, you should configure your |.latexmkrc| so that \pytex\ always runs after the first \LaTeX\ run.
+%
+%
+%
% \section{\texttt{depythontex}}
% \label{sec:depythontex}
%
@@ -1089,7 +1256,7 @@
%
% \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 |--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.
%
@@ -1101,8 +1268,8 @@
% 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 |-o| |--output| The name of the output file. If no name is given, the converted file is written to |stdout|.
% \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.
%
@@ -1213,7 +1380,7 @@
%
% 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.)
+% 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()| or an |open()| 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.
%
@@ -1224,13 +1391,29 @@
%
% 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.)
+% The utilities ``class'' is called |JuliaTeXUtils| (it is actually a composite type, very similar to a struct), 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 or an |open()| 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.
+% A family of commands and environments for Julia 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{Octave}
+%
+% Support for Octave was added in v0.13. Octave support should be at almost the same level as that for Python. Parsing of stderr for synchronization is successful in most cases but not ideal; this will be improved in a future release by a rewrite of the stderr parser.
+%
+% Octave does not have a genuine utilities class, since it only supports |@CLASS| classes and does not yet support newer MATLAB-style |classdef|. As a result of this limited support for classes, there is a struct |octavetex| rather than a utilities class instance |octavetex|. What would have been attributes of a utilities class instance are instead fields of the struct. What would have been methods of a class are instead anonymous functions. This allows |octavetex| to be used in most respects as if it were a class instance, especially insofar as syntax is concerned.
+%
+% There are no |set_formatter()| or |open()| methods.
+%
+% If any ``methods'' need to be overwritten, the simplest approach is probably to define a function and then set the appropriate struct field to an anonymous function that will call that function. For example, to replace the default |octavetex.before()|, we might define a function |before()|, and then use the command |octavetex.before = @() before();|. Of course, if the function is sufficiently short, it will be simpler just to put everything in the anonymous function: |octavetex.before = @() <expression>;|
+%
+% A family of commands and environments for Octave is not created by default; the base name |octave| is provided.
+%
+% When |\setpythontexcontext| is used, it must be accessed as struct fields, of the form |octavetex.context.<name>|.
+%
+%
+%
% \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.
@@ -1241,13 +1424,13 @@
%
% \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.
+% \pytex\ executes user code by inserting it in a script template. Replacement fields in the template are indicated by curly braces: |{|\meta{field}|}|.\footnote{This follows Python's \href{http://docs.python.org/2/library/string.html\#formatstrings}{format string syntax}. Literal curly braces are obtained by doubling.} Space between \meta{field} and the braces is not allowed. 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 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:
@@ -1255,26 +1438,28 @@
% \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.
+% \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 the names of 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|).
+% \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 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 |{family}|, |{session}|, and |{restart}|; all should be strings. These variables should be named after the fields if possible (for example, |pytex.family|). These variables are not strictly necessary, but they allow user code to access information about its origin on the \TeX\ side.
+% \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 Set additional \LaTeX-related utilities variables: |{command}|, |{context}|, |{args}|, |{instance}|, |{line}|. They are not required, but make possible closer \LaTeX\ integration. |{args}| is not yet supported on the \LaTeX\ side, but will allow arguments from \LaTeX\ commands to be passed to user code.
+%
+% All utilities variables should be stored as strings, except for |context| and |args|. If possible, these should be dictionaries or equivalent associative arrays of string keys that point to string values. The dictionaries should be created by processing |{context}| and |{args}| into comma-separated lists of key-value pairs. For example, if |{context}| is the string ``|k1=v1, k2=v2|'', then |pytex.context| should be a dictionary, and |pytex.context['k1']| should yield the string ``|v1|''. The key-value pairs may optionally be accessed as attributes, when this is possible with a given language. For example, |pytex.context.k1| could yield the string ``|v1|''.
+% \item Write a delimiter |{stdoutdelim}| to |stdout| and a delimiter |{stderrdelim}| 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.\footnote{This way, we don't have to assume that all languages will use \texttt{\string\n} for the newline character.} For example, something like |"{stderrdelim}\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.
+% \item Have a |{code}| field into which the current chunk of user code is inserted.
+% \item Call |after()|.
% \end{itemize}
%
% \subsubsection{The \texttt{CodeEngine} class}
@@ -1285,28 +1470,28 @@
% \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 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 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 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.
+% \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, if files are created with the same base name as the script; this could be useful with compiled languages, which might have a |{file}.|\meta{extension} that ultimately results in a |{file}.out|, |{file}.exe|, etc.
% \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}}:'])
+%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.
+% 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.
@@ -1324,9 +1509,63 @@
% \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 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 option.
+%
+% You may also run \pytex\ with the |--debug| option to launch the default debugger, or use the debugger of your choice by adding code that launches a debugger and then running \pytex\ with the |--interactive| option. See Section~\ref{sec:installing-and-running:compiling} for more details.
%
% \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}|.
+%
+% \item The |tabular| environment can conflict with \pytex\ under some circumstances, due to how |tabular| functions. Among other things, printing within a |tabular| environment can cause errors, because printing involves bringing in external content via |\InputIfFileExists|, but that macro is not expandable.\footnote{For more information, see \href{http://tex.stackexchange.com/questions/50820/expandable-version-of-inputiffileexists-or-iffileexists}{this}, \href{http://tex.stackexchange.com/questions/50828/execute-non-expandable-code-inside-a-tabular-environment}{this}, and \href{http://tex.stackexchange.com/questions/50694/cannot-use-toprule-when-doing-input-inside-tabular-why}{this}.} There are a few different ways to work around the limitations of |tabular|.
+% \begin{itemize}
+%\item Put the printed content in a macro definition, and use the macro in |tabular|. You will have to create a dummy version of the macro, to avoid errors before the macro is defined by \pytex. An example is given below. The |\global\def| is needed so that the macro is defined outside of the |pycode| environment.
+%\begin{verbatim}
+%\let\row\relax
+%\begin{pycode}
+%print("\\global\\def\\row{a & b & c & d & e \\\\}")
+%\end{pycode}
+%
+%\begin{tabular}{|c|c|c|c|c|}
+%\row
+%\end{tabular}
+%\end{verbatim}
+%
+% \item Use |\py|. The end-of-row |\\| must be outside of the command. Example:
+%\begin{verbatim}
+%\begin{tabular}{|c|c|c|c|c|}
+%\py{"a & b & c & d & e"} \\
+%\end{tabular}
+%\end{verbatim}
+% \end{itemize}
+% \item \pytex\ commands like |\py| won't work inside |siunitx| macros, because |\py| and company aren't fully expandable.\footnote{For more details, see \href{http://tex.stackexchange.com/questions/35039/why-isnt-everything-expandable}{this}, \href{http://tex.stackexchange.com/questions/66118/advantages-and-disadvantages-of-fully-expandable-macros}{this}, and \href{http://tex.stackexchange.com/questions/164918/how-do-i-use-ifstreqcase-within-a-siunitx-command}{this}.} There are different ways to work around this; some examples are shown below.
+%\begin{verbatim}
+%\documentclass{article}
+%\usepackage{siunitx}
+%\usepackage{pythontex}
+%
+%\begin{pycode}
+%def SI(var, unit):
+% return '\\SI{' + str(var) + '}{' + unit + '}'
+%\end{pycode}
+%
+%\newcommand{\pySI}[2]{\py{'\\SI{' + str(#1) + '}{#2}'}}
+%
+%\begin{document}
+%\pyc{y = 4}
+%
+%The value of y is \py{SI(y, r'\metre')}.
+%
+%The value of y is \pySI{y}{\metre}.
+%
+%\end{document}
+%\end{verbatim}
+% Another example, this time using SymPy:
+%\begin{verbatim}
+%\newcommand{\sympySI}[2]{\sympy{SI(#1,r"#2")}}
+%\begin{sympycode}
+%def SI(var, unit):
+% return '\\SI{{{0}}}{{{1}}}'.format(N(var, 4), unit)
+%\end{sympycode}
+%\end{verbatim}
% \end{itemize}
%
%
@@ -1350,7 +1589,7 @@
% \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.
+% \item Continue adding support for additional languages. Under consideration: Perl, Lua, MATLAB, Mathematica, Sage, R, Octave.
% \end{itemize}
%
% \subsubsection{Modifications to consider}
@@ -1399,7 +1638,7 @@
% 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}
+\newcommand{\pytx@packageversion}{v0.13}
% \end{macrocode}
%
% \subsection{Required packages}
@@ -1833,7 +2072,7 @@
%
% 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.
+% 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. Two separate tests for LuaTeX are performed: comparison with an undefined macro |\pytx@undefined|, which will work under most circumstances, and comparison with |\relax|, which serves as a backup in case a test for LuaTeX by another package |\let|'s |\directlua| to |\relax| (for example, an |\expandafter\ifx\csname directlua\endcsname\relax| test).
%
% \begin{macrocode}
\def\pytx@SVMCR#1{%
@@ -1851,7 +2090,15 @@
!expandafter!scantokens!expandafter{#1!empty}}%
}%
!endgroup
-\expandafter\ifx\csname directlua\endcsname\relax\else
+\ifx\directlua\pytx@undefined\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
+\ifx\directlua\relax\else
\def\pytx@SVMCR#1 {%
\edef\pytx@tmp{\csname #1\endcsname}%
\pytx@SVMCR@i}
@@ -1861,22 +2108,32 @@
\fi
% \end{macrocode}\end{macro}
%
+% \begin{macro}{pytx@Verbatim}
+% \begin{macro}{pytx@SaveVerbatim}
+% We need custom versions of |fancyvrb|'s |Verbatim| and |SaveVerbatim| environments, because we don't want to have to worry about the possibility of these environments containing literal |Verbatim| and |SaveVerbatim| environments.
+% \begin{macrocode}
+\DefineVerbatimEnvironment{pytx@Verbatim}{Verbatim}{}
+\DefineVerbatimEnvironment{pytx@SaveVerbatim}{SaveVerbatim}{}
+% \end{macrocode}
+% \end{macro}\end{macro}
+%
%
% \subsubsection{Code context}
-% \begin{macro}{\pytx@context}\begin{macro}{\pytx@SetContext}\begin{macro}{\definepythontexcontext}
+% \begin{macro}{\pytx@context}\begin{macro}{\pytx@SetContext}\begin{macro}{\setpythontexcontext}
% 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.
+% 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 |\setpythontexcontext| 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]{%
+\newcommand{\setpythontexcontext}[1]{%
+ \Depythontex{cmd:setpythontexcontext:m:n}%
\def\pytx@SetContext{%
\edef\pytx@context{#1}%
}%
}
-\definepythontexcontext{}
-\@onlypreamble\definepythontexcontext
+\setpythontexcontext{}
+\@onlypreamble\setpythontexcontext
% \end{macrocode}
% \end{macro}\end{macro}\end{macro}
%
@@ -2003,7 +2260,14 @@
% \begin{macrocode}
\AtBeginDocument{%
\makeatletter
- \InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxmcr}{}{}%
+ \InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxmcr}{}%
+ {\ifstrempty{\pytx@outputdir}%
+ {\typeout{No file \pytx@jobname.pytxmcr.}}%
+ {}%
+ \IfStrEq{\pytx@outputdir}{.}%
+ {\typeout{No file \pytx@jobname.pytxmcr.}}%
+ {\typeout{No file \pytx@outputdir/\pytx@jobname.pytxmcr.}}%
+ \typeout{Run \pytx@packagename\space to create it.}}%
\makeatother
}
% \end{macrocode}
@@ -2064,7 +2328,7 @@
% \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.}
+% 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.\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. Originally, the external version used a fixed instance, but that conflicted with the \texttt{fancyvrb} options \texttt{firstline} and \texttt{lastline}, so instances had to be added.}
% \begin{macrocode}
\def\pytx@argsrun{}
\def\pytx@argspprint{}
@@ -2084,7 +2348,7 @@
{\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%
+ \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%
@@ -2138,6 +2402,8 @@
% \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.
%
+% There is an additional line-numbering issue when the |firstline| option is used with |SaveVerbatim|. This is fixed by globally reseting |\FV@CodeLineNo| to zero. That was originally done in |fancyvrb|, via |\FV@FormattingPrep|, but this macro is commented out in the current version of |fancyvrb|, which throws off line numbering.
+%
% 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}
@@ -2145,6 +2411,7 @@
% \begin{macrocode}
\def\pytx@FVB@SaveVerbatim#1{%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
+ \global\FV@CodeLineNo\z@
\@bsphack
\begingroup
\FV@UseKeyValues
@@ -3084,6 +3351,7 @@
% \begin{macrocode}
\def\pytx@BeginEnvPyg@i[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
\let\FVB@VerbatimOut\pytx@FVB@VerbatimOut
\let\FVE@VerbatimOut\pytx@FVE@VerbatimOut
@@ -3308,6 +3576,7 @@
% \begin{macrocode}
\def\pytx@BeginConsEnvFV@i[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
\let\FVB@VerbatimOut\pytx@FVB@VerbatimOut
\let\FVE@VerbatimOut\pytx@FVE@VerbatimOut
@@ -3523,6 +3792,8 @@
\ifstrequal{#1}{rb}{\makepythontexfamily[pyglexer=ruby]{rb}}{}%
\ifstrequal{#1}{julia}{\makepythontexfamily[pyglexer=julia]{julia}}{}%
\ifstrequal{#1}{jl}{\makepythontexfamily[pyglexer=julia]{jl}}{}%
+ \ifstrequal{#1}{matlab}{\makepythontexfamily[pyglexer=matlab]{matlab}}{}%
+ \ifstrequal{#1}{octave}{\makepythontexfamily[pyglexer=octave]{octave}}{}%
}
\expandafter\docsvlist\expandafter{\pytx@families}
% \end{macrocode}
@@ -3557,7 +3828,7 @@
{\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}}%
+ Use \string\setpythontexlistingenv\space to create a custom listing environment}}%
\else
\ifbool{pytx@listingenv}{}{\DeclareFloatingEnvironment[fileext=lopytx]{listing}}
\fi
@@ -3663,6 +3934,7 @@
% \begin{macrocode}
\def\pytx@BEPygmentsFV[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\begingroup
\obeylines
\pytx@BEPygmentsFV@i
@@ -3733,6 +4005,7 @@
% \begin{macrocode}
\def\pytx@BEPygmentsPyg[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\begingroup
\obeylines
\pytx@BEPygmentsPyg@i
@@ -3769,7 +4042,7 @@
%
% \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.
+% 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|.
%
% 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.
%
@@ -3786,6 +4059,8 @@
\pytx@SetGroupVerb
\edef\pytx@session{EXT:##3}%
\pytx@CheckPygmentsInit{##2}%
+ \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}%
+ \pytx@CheckCounter{\pytx@counter}%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
\begingroup
\DepyListing %Always must be in a group
@@ -3796,32 +4071,42 @@
{\PackageWarning{\pytx@packagename}{Input file <##3> doesn't exist}}%
\endgroup
\setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}%
+ \stepcounter{\pytx@counter}%
}%
}
\def\pytx@MakePygmentsInputPyg{
\newcommand{\inputpygments}[3][]{%
+ \begingroup
\edef\pytx@type{PYG##2}%
\edef\pytx@cmd{verbatim}%
\pytx@SetContext
\pytx@SetGroupVerb
+ \def\pytx@argspprint{##1}%
\edef\pytx@session{EXT:##3}%
\pytx@CheckPygmentsInit{##2}%
+ \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}%
+ \pytx@CheckCounter{\pytx@counter}%
\pytx@WriteCodefileInfoExt
\IfFileExists{##3}{}{\PackageWarning{\pytx@packagename}%
- {Input file <##3> does not exist}}
+ {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}%
+ \ifcsname FV@SV@pytx@\pytx@type @\pytx@session @\pytx@group
+ @\arabic{\pytx@counter}\endcsname
+ \UseVerbatim[##1]{pytx@\pytx@type @\pytx@session @\pytx@group
+ @\arabic{\pytx@counter}}%
\else
- \InputIfFileExists{\pytx@outputdir/##3_##2.pygtex}{}%
+ \InputIfFileExists{\pytx@outputdir/\pytx@type_##3_\pytx@group
+ _\arabic{\pytx@counter}.pygtex}{}%
{\textbf{??~\pytx@packagename~??}%
\PackageWarning{\pytx@packagename}{Non-existent Pygments content}}%
\fi
\endgroup
\setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}%
+ \stepcounter{\pytx@counter}%
+ \endgroup
}%
}
% \end{macrocode}
diff --git a/Master/texmf-dist/source/latex/pythontex/pythontex_install.bat b/Master/texmf-dist/source/latex/pythontex/pythontex_install.bat
new file mode 100755
index 00000000000..e32fa0ba210
--- /dev/null
+++ b/Master/texmf-dist/source/latex/pythontex/pythontex_install.bat
@@ -0,0 +1,4 @@
+@echo off
+@setlocal enableextensions
+@cd /d "%~dp0"
+pythontex_install.py %*
diff --git a/Master/texmf-dist/tex/latex/pythontex/pythontex.sty b/Master/texmf-dist/tex/latex/pythontex/pythontex.sty
index a71b4f48d2d..500c20cd289 100644
--- a/Master/texmf-dist/tex/latex/pythontex/pythontex.sty
+++ b/Master/texmf-dist/tex/latex/pythontex/pythontex.sty
@@ -20,10 +20,10 @@
%%
\NeedsTeXFormat{LaTeX2e}[1999/12/01]
\ProvidesPackage{pythontex}
- [2013/08/26 Version~0.12]
+ [2014/07/14 Version~0.13 ]
\newcommand{\pytx@packagename}{PythonTeX}
-\newcommand{\pytx@packageversion}{v0.12}
+\newcommand{\pytx@packageversion}{v0.13}
\RequirePackage{fancyvrb}
\RequirePackage{etex}
\RequirePackage{etoolbox}
@@ -230,7 +230,7 @@
!expandafter!scantokens!expandafter{#1!empty}}%
}%
!endgroup
-\expandafter\ifx\csname directlua\endcsname\relax\else
+\ifx\directlua\pytx@undefined\else
\def\pytx@SVMCR#1 {%
\edef\pytx@tmp{\csname #1\endcsname}%
\pytx@SVMCR@i}
@@ -238,13 +238,24 @@
\expandafter\gdef\pytx@tmp{#1}%
}
\fi
-\newcommand{\definepythontexcontext}[1]{%
+\ifx\directlua\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
+\DefineVerbatimEnvironment{pytx@Verbatim}{Verbatim}{}
+\DefineVerbatimEnvironment{pytx@SaveVerbatim}{SaveVerbatim}{}
+\newcommand{\setpythontexcontext}[1]{%
+ \Depythontex{cmd:setpythontexcontext:m:n}%
\def\pytx@SetContext{%
\edef\pytx@context{#1}%
}%
}
-\definepythontexcontext{}
-\@onlypreamble\definepythontexcontext
+\setpythontexcontext{}
+\@onlypreamble\setpythontexcontext
\newcommand{\restartpythontexsession}[1]{%
\Depythontex{cmd:restartpythontexsession:m:n}%
\def\pytx@SetGroup{%
@@ -304,7 +315,14 @@
}
\AtBeginDocument{%
\makeatletter
- \InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxmcr}{}{}%
+ \InputIfFileExists{\pytx@outputdir/\pytx@jobname.pytxmcr}{}%
+ {\ifstrempty{\pytx@outputdir}%
+ {\typeout{No file \pytx@jobname.pytxmcr.}}%
+ {}%
+ \IfStrEq{\pytx@outputdir}{.}%
+ {\typeout{No file \pytx@jobname.pytxmcr.}}%
+ {\typeout{No file \pytx@outputdir/\pytx@jobname.pytxmcr.}}%
+ \typeout{Run \pytx@packagename\space to create it.}}%
\makeatother
}
\newwrite\pytx@codefile
@@ -350,7 +368,7 @@
{\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%
+ \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%
@@ -377,6 +395,7 @@
\newcounter{pytx@FancyVerbLineTemp}
\def\pytx@FVB@SaveVerbatim#1{%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
+ \global\FV@CodeLineNo\z@
\@bsphack
\begingroup
\FV@UseKeyValues
@@ -935,6 +954,7 @@
}%
\def\pytx@BeginEnvPyg@i[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
\let\FVB@VerbatimOut\pytx@FVB@VerbatimOut
\let\FVE@VerbatimOut\pytx@FVE@VerbatimOut
@@ -1059,6 +1079,7 @@
}%
\def\pytx@BeginConsEnvFV@i[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
\let\FVB@VerbatimOut\pytx@FVB@VerbatimOut
\let\FVE@VerbatimOut\pytx@FVE@VerbatimOut
@@ -1226,6 +1247,8 @@
\ifstrequal{#1}{rb}{\makepythontexfamily[pyglexer=ruby]{rb}}{}%
\ifstrequal{#1}{julia}{\makepythontexfamily[pyglexer=julia]{julia}}{}%
\ifstrequal{#1}{jl}{\makepythontexfamily[pyglexer=julia]{jl}}{}%
+ \ifstrequal{#1}{matlab}{\makepythontexfamily[pyglexer=matlab]{matlab}}{}%
+ \ifstrequal{#1}{octave}{\makepythontexfamily[pyglexer=octave]{octave}}{}%
}
\expandafter\docsvlist\expandafter{\pytx@families}
\newbool{pytx@listingenv}
@@ -1241,7 +1264,7 @@
{\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}}%
+ Use \string\setpythontexlistingenv\space to create a custom listing environment}}%
\else
\ifbool{pytx@listingenv}{}{\DeclareFloatingEnvironment[fileext=lopytx]{listing}}
\fi
@@ -1298,6 +1321,7 @@
}
\def\pytx@BEPygmentsFV[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\begingroup
\obeylines
\pytx@BEPygmentsFV@i
@@ -1347,6 +1371,7 @@
}
\def\pytx@BEPygmentsPyg[#1]{%
\def\pytx@fvopttmp{#1}%
+ \def\pytx@argspprint{#1}%
\begingroup
\obeylines
\pytx@BEPygmentsPyg@i
@@ -1377,6 +1402,8 @@
\pytx@SetGroupVerb
\edef\pytx@session{EXT:##3}%
\pytx@CheckPygmentsInit{##2}%
+ \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}%
+ \pytx@CheckCounter{\pytx@counter}%
\setcounter{pytx@FancyVerbLineTemp}{\value{FancyVerbLine}}%
\begingroup
\DepyListing %Always must be in a group
@@ -1387,32 +1414,42 @@
{\PackageWarning{\pytx@packagename}{Input file <##3> doesn't exist}}%
\endgroup
\setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}%
+ \stepcounter{\pytx@counter}%
}%
}
\def\pytx@MakePygmentsInputPyg{
\newcommand{\inputpygments}[3][]{%
+ \begingroup
\edef\pytx@type{PYG##2}%
\edef\pytx@cmd{verbatim}%
\pytx@SetContext
\pytx@SetGroupVerb
+ \def\pytx@argspprint{##1}%
\edef\pytx@session{EXT:##3}%
\pytx@CheckPygmentsInit{##2}%
+ \xdef\pytx@counter{pytx@\pytx@type @\pytx@session @\pytx@group}%
+ \pytx@CheckCounter{\pytx@counter}%
\pytx@WriteCodefileInfoExt
\IfFileExists{##3}{}{\PackageWarning{\pytx@packagename}%
- {Input file <##3> does not exist}}
+ {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}%
+ \ifcsname FV@SV@pytx@\pytx@type @\pytx@session @\pytx@group
+ @\arabic{\pytx@counter}\endcsname
+ \UseVerbatim[##1]{pytx@\pytx@type @\pytx@session @\pytx@group
+ @\arabic{\pytx@counter}}%
\else
- \InputIfFileExists{\pytx@outputdir/##3_##2.pygtex}{}%
+ \InputIfFileExists{\pytx@outputdir/\pytx@type_##3_\pytx@group
+ _\arabic{\pytx@counter}.pygtex}{}%
{\textbf{??~\pytx@packagename~??}%
\PackageWarning{\pytx@packagename}{Non-existent Pygments content}}%
\fi
\endgroup
\setcounter{FancyVerbLine}{\value{pytx@FancyVerbLineTemp}}%
+ \stepcounter{\pytx@counter}%
+ \endgroup
}%
}
\newcommand{\setpygmentsfv}[2][]{%