summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/pythontex/pythontex_utils.py
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2013-09-12 23:36:12 +0000
committerKarl Berry <karl@freefriends.org>2013-09-12 23:36:12 +0000
commit88cc357fc8d3e1b57e21eb762fef36e4ef2dd3f4 (patch)
tree51244daaeef0622ddd48de2d1464daa5b235b1c1 /Master/texmf-dist/scripts/pythontex/pythontex_utils.py
parent3ce8f395f1980ebc8f4e95d66e79ac7f29f99222 (diff)
pythontex (import from $0 symlink works after all)
git-svn-id: svn://tug.org/texlive/trunk@31638 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/pythontex/pythontex_utils.py')
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_utils.py372
1 files changed, 372 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_utils.py b/Master/texmf-dist/scripts/pythontex/pythontex_utils.py
new file mode 100755
index 00000000000..75d3115a395
--- /dev/null
+++ b/Master/texmf-dist/scripts/pythontex/pythontex_utils.py
@@ -0,0 +1,372 @@
+# -*- coding: utf-8 -*-
+'''
+PythonTeX utilities class for Python scripts.
+
+The utilities class provides variables and methods for the individual
+Python scripts created and executed by PythonTeX. An instance of the class
+named "pytex" is automatically created in each individual script.
+
+Copyright (c) 2012-2013, Geoffrey M. Poore
+All rights reserved.
+Licensed under the BSD 3-Clause License:
+ http://www.opensource.org/licenses/BSD-3-Clause
+
+'''
+
+
+# Imports
+import sys
+import warnings
+
+# Most imports are only needed for SymPy; these are brought in via
+# "lazy import." Importing unicode_literals here shouldn't ever be necessary
+# under Python 2. If unicode_literals is imported in the main script, then
+# all strings in this script will be treated as bytes, and the main script
+# will try to decode the strings from this script as necessary. The decoding
+# shouldn't cause any problems, since all strings in this file may be decoded
+# as valid ASCII. (The actual file is encoded in utf-8, but only characters
+# within the ASCII subset are actually used).
+
+
+class PythonTeXUtils(object):
+ '''
+ A class of PythonTeX utilities.
+
+ Provides variables for keeping track of TeX-side information, and methods
+ for formatting and saving data.
+
+ The following variables and methods will be created within instances
+ of the class during execution.
+
+ String variables for keeping track of TeX information. Most are
+ actually needed; the rest are included for completeness.
+ * input_family
+ * input_session
+ * input_restart
+ * input_command
+ * input_context
+ * input_args
+ * input_instance
+ * input_line
+
+ Future file handle for output that is saved via macros
+ * macrofile
+
+ Future formatter function that is used to format output
+ * formatter
+ '''
+
+ def __init__(self, fmtr='str'):
+ '''
+ Initialize
+ '''
+ self.set_formatter(fmtr)
+
+
+ # We need a context-aware interface to SymPy's latex printer. The
+ # appearance of typeset math should depend on where it appears in a
+ # document. (We will refer to the latex printer, rather than the LaTeX
+ # printer, because the two are separate. Compare sympy.printing.latex
+ # and sympy.galgebra.latex_ex.)
+ #
+ # Creating this interface takes some work. We don't want to import
+ # anything from SymPy unless it is actually used, to keep things clean and
+ # fast.
+
+ # First we create a tuple containing all LaTeX math styles. These are
+ # the contexts that SymPy's latex printer must adapt to.
+ # The style order doesn't matter, but it corresponds to that of \mathchoice
+ _sympy_latex_styles = ('display', 'text', 'script', 'scriptscript')
+
+ # Create the public functions for the user, and private functions that
+ # they call. Two layers are necessary, because we need to be able to
+ # redefine the functions that do the actual work, once things are
+ # initialized. But we don't want to redefine the public functions, since
+ # that could cause problems if the user defines a new function to be one
+ # of the public functions--the user's function would not change when
+ # the method was redefined.
+ def _sympy_latex(self, expr, **settings):
+ self._init_sympy_latex()
+ return self._sympy_latex(expr, **settings)
+
+ def sympy_latex(self, expr, **settings):
+ return self._sympy_latex(expr, **settings)
+
+ def _set_sympy_latex(self, style, **kwargs):
+ self._init_sympy_latex()
+ self._set_sympy_latex(style, **kwargs)
+
+ def set_sympy_latex(self, style, **kwargs):
+ self._set_sympy_latex(style, **kwargs)
+ # Temporary compatibility with deprecated methods
+ def init_sympy_latex(self):
+ warnings.warn('Method init_sympy_latex() is deprecated; init is now automatic.')
+ self._init_sympy_latex()
+
+ # Next we create a method that initializes the actual context-aware
+ # interface to SymPy's latex printer.
+ def _init_sympy_latex(self):
+ '''
+ Initialize a context-aware interface to SymPy's latex printer.
+
+ This consists of creating the dictionary of settings and creating the
+ sympy_latex method that serves as an interface to SymPy's
+ LatexPrinter. This last step is actually performed by calling
+ self._make_sympy_latex().
+ '''
+ # Create dictionaries of settings for different contexts.
+ #
+ # Currently, the main goal is to use pmatrix (or an equivalent)
+ # in \displaystyle contexts, and smallmatrix in \textstyle,
+ # \scriptstyle (superscript or subscript), and \scriptscriptstyle
+ # (superscript or subscript of a superscript or subscript)
+ # contexts. Basically, we want matrix size to automatically
+ # scale based on context. It is expected that additional
+ # customization may prove useful as SymPy's LatexPrinter is
+ # further developed.
+ #
+ # The 'fold_frac_powers' option is probably the main other
+ # setting that might sometimes be nice to invoke in a
+ # context-dependent manner.
+ #
+ # In the default settings below, all matrices are set to use
+ # parentheses rather than square brackets. This is largely a
+ # matter of personal preference. The use of parentheses is based
+ # on the rationale that parentheses are less easily confused with
+ # the determinant and are easier to write by hand than are square
+ # brackets. The settings for 'script' and 'scriptscript' are set
+ # to those of 'text', since all of these should in general
+ # require a more compact representation of things.
+ self._sympy_latex_settings = {'display': {'mat_str': 'pmatrix', 'mat_delim': None},
+ 'text': {'mat_str': 'smallmatrix', 'mat_delim': '('},
+ 'script': {'mat_str': 'smallmatrix', 'mat_delim': '('},
+ 'scriptscript': {'mat_str': 'smallmatrix', 'mat_delim': '('} }
+ # Now we create a function for updating the settings.
+ #
+ # Note that EVERY time the settings are changed, we must call
+ # self._make_sympy_latex(). This is because the _sympy_latex()
+ # method is defined based on the settings, and every time the
+ # settings change, it may need to be redefined. It would be
+ # possible to define _sympy_latex() so that its definition remained
+ # constant, simply drawing on the settings. But most common
+ # combinations of settings allow more efficient versions of
+ # _sympy_latex() to be defined.
+ def _set_sympy_latex(style, **kwargs):
+ if style in self._sympy_latex_styles:
+ self._sympy_latex_settings[style].update(kwargs)
+ elif style == 'all':
+ for s in self._sympy_latex_styles:
+ self._sympy_latex_settings[s].update(kwargs)
+ else:
+ warnings.warn('Unknown LaTeX math style ' + str(style))
+ self._make_sympy_latex()
+ self._set_sympy_latex = _set_sympy_latex
+
+ # Now that the dictionaries of settings have been created, and
+ # the function for modifying the settings is in place, we are ready
+ # to create the actual interface.
+ self._make_sympy_latex()
+
+ # Finally, create the actual interface to SymPy's LatexPrinter
+ def _make_sympy_latex(self):
+ '''
+ Create a context-aware interface to SymPy's LatexPrinter class.
+
+ This is an interface to the LatexPrinter class, rather than
+ to the latex function, because the function is simply a
+ wrapper for accessing the class and because settings may be
+ passed to the class more easily.
+
+ Context dependence is accomplished via LaTeX's \mathchoice macro.
+ This macros takes four arguments:
+ \mathchoice{<display>}{<text>}{<script>}{<scriptscript>}
+ All four arguments are typeset by LaTeX, and then the appropriate one
+ is actually typeset in the document based on the current style. This
+ may seem like a very inefficient way of doing things, but this
+ approach is necessary because LaTeX doesn't know the math style at a
+ given point until after ALL mathematics have been typeset. This is
+ because macros such as \over and \atop change the math style of things
+ that PRECEDE them. See the following discussion for more information:
+ http://tex.stackexchange.com/questions/1223/is-there-a-test-for-the-different-styles-inside-maths-mode
+
+ The interface takes optional settings. These optional
+ settings override the default context-dependent settings.
+ Accomplishing this mixture of settings requires (deep)copying
+ the default settings, then updating the copies with the optional
+ settings. This leaves the default settings intact, with their
+ original values, for the next usage.
+
+ The interface is created in various ways depending on the specific
+ combination of context-specific settings. While a general, static
+ interface could be created, that would involve invoking LatexPrinter
+ four times, once for each math style. It would also require that
+ LaTeX process a \mathchoice macro for everything returned by
+ _sympy_latex(), which would add more inefficiency. In practice, there
+ will generally be enough overlap between the different settings, and
+ the settings will be focused enough, that more efficient
+ implementations of _sympy_latex() are possible.
+
+ Note that we perform a "lazy import" here. We don't want to import
+ the LatexPrinter unless we are sure to use it, since the import brings
+ along a number of other dependencies from SymPy. We don't want
+ unnecessary overhead from SymPy imports.
+ '''
+ # sys has already been imported
+ import copy
+ try:
+ from sympy.printing.latex import LatexPrinter
+ except ImportError:
+ sys.exit('Could not import from SymPy')
+
+ # Go through a number of possible scenarios, to create an efficient
+ # implementation of sympy_latex()
+ if all(self._sympy_latex_settings[style] == {} for style in self._sympy_latex_styles):
+ def _sympy_latex(expr, **settings):
+ '''
+ Deal with the case where there are no context-specific
+ settings.
+ '''
+ return LatexPrinter(settings).doprint(expr)
+ elif all(self._sympy_latex_settings[style] == self._sympy_latex_settings['display'] for style in self._sympy_latex_styles):
+ def _sympy_latex(expr, **settings):
+ '''
+ Deal with the case where all settings are identical, and thus
+ the settings are really only being used to set defaults,
+ rather than context-specific behavior.
+
+ Check for empty settings, so as to avoid deepcopy
+ '''
+ if not settings:
+ return LatexPrinter(self._sympy_latex_settings['display']).doprint(expr)
+ else:
+ final_settings = copy.deepcopy(self._sympy_latex_settings['display'])
+ final_settings.update(settings)
+ return LatexPrinter(final_settings).doprint(expr)
+ elif all(self._sympy_latex_settings[style] == self._sympy_latex_settings['text'] for style in ('script', 'scriptscript')):
+ def _sympy_latex(expr, **settings):
+ '''
+ Deal with the case where only 'display' has different settings.
+
+ This should be the most common case.
+ '''
+ if not settings:
+ display = LatexPrinter(self._sympy_latex_settings['display']).doprint(expr)
+ text = LatexPrinter(self._sympy_latex_settings['text']).doprint(expr)
+ else:
+ display_settings = copy.deepcopy(self._sympy_latex_settings['display'])
+ display_settings.update(settings)
+ display = LatexPrinter(display_settings).doprint(expr)
+ text_settings = copy.deepcopy(self._sympy_latex_settings['text'])
+ text_settings.update(settings)
+ text = LatexPrinter(text_settings).doprint(expr)
+ if display == text:
+ return display
+ else:
+ return r'\mathchoice{' + display + '}{' + text + '}{' + text + '}{' + text + '}'
+ else:
+ def _sympy_latex(expr, **settings):
+ '''
+ If all attempts at simplification fail, create the most
+ general interface.
+
+ The main disadvantage here is that LatexPrinter is invoked
+ four times and we must create many temporary variables.
+ '''
+ if not settings:
+ display = LatexPrinter(self._sympy_latex_settings['display']).doprint(expr)
+ text = LatexPrinter(self._sympy_latex_settings['text']).doprint(expr)
+ script = LatexPrinter(self._sympy_latex_settings['script']).doprint(expr)
+ scriptscript = LatexPrinter(self._sympy_latex_settings['scriptscript']).doprint(expr)
+ else:
+ display_settings = copy.deepcopy(self._sympy_latex_settings['display'])
+ display_settings.update(settings)
+ display = LatexPrinter(display_settings).doprint(expr)
+ text_settings = copy.deepcopy(self._sympy_latex_settings['text'])
+ text_settings.update(settings)
+ text = LatexPrinter(text_settings).doprint(expr)
+ script_settings = copy.deepcopy(self._sympy_latex_settings['script'])
+ script_settings.update(settings)
+ script = LatexPrinter(script_settings).doprint(expr)
+ scriptscript_settings = copy.deepcopy(self._sympy_latex_settings['scripscript'])
+ scriptscript_settings.update(settings)
+ scriptscript = LatexPrinter(scriptscript_settings).doprint(expr)
+ if display == text and display == script and display == scriptscript:
+ return display
+ else:
+ return r'\mathchoice{' + display + '}{' + text + '}{' + script + '}{' + scriptscript+ '}'
+ self._sympy_latex = _sympy_latex
+
+ # Now we are ready to create non-SymPy formatters and a method for
+ # setting formatters
+ def identity_formatter(self, expr):
+ '''
+ For generality, we need an identity formatter, a formatter that does
+ nothing to its argument and simply returns it unchanged.
+ '''
+ return expr
+
+ def set_formatter(self, fmtr='str'):
+ '''
+ Set the formatter method.
+
+ This is used to process output that is brought in via macros. It is
+ also available for the user in formatting printed or saved output.
+ '''
+ if fmtr == 'str':
+ if sys.version_info[0] == 2:
+ self.formatter = unicode
+ else:
+ self.formatter = str
+ elif fmtr == 'sympy_latex':
+ self.formatter = self.sympy_latex
+ elif fmtr in ('None', 'none', 'identity') or fmtr is None:
+ self.formatter = self.identity_formatter
+ else:
+ raise ValueError('Unsupported formatter type')
+
+ # We need functions that can be executed immediately before and after
+ # each chunk of code. By default, these should do nothing; they are for
+ # user customization, or customization via packages.
+ def before(self):
+ pass
+ def after(self):
+ pass
+
+
+ # We need a way to keep track of dependencies
+ # We create a list that stores specified dependencies, and a method that
+ # adds dependencies to the list. The contents of this list must be
+ # written to stdout at the end of the file, to be transmitted back to the
+ # main script. So we create a method that prints them to stdout. This is
+ # called via a generic cleanup method that is always invoked at the end of
+ # the script.
+ _dependencies = list()
+ def add_dependencies(self, *args):
+ self._dependencies.extend(list(args))
+ def _save_dependencies(self):
+ print('=>PYTHONTEX:DEPENDENCIES#')
+ if self._dependencies:
+ for dep in self._dependencies:
+ print(dep)
+
+ # We need a way to keep track of created files, so that they can be
+ # automatically cleaned up. By default, all files are created within the
+ # pythontex-files_<jobname> folder, and are thus contained. If a custom
+ # working directory is used, or files are otherwise created in a custom
+ # location, it may be desirable to track them and keep them cleaned up.
+ # Furthermore, even when files are contained in the default directory, it
+ # may be desirable to delete files when they are no longer needed due to
+ # program changes, renaming, etc.
+ _created = list()
+ def add_created(self, *args):
+ self._created.extend(list(args))
+ def _save_created(self):
+ print('=>PYTHONTEX:CREATED#')
+ if self._created:
+ for creation in self._created:
+ print(creation)
+
+ def cleanup(self):
+ self._save_dependencies()
+ self._save_created()
+