summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/scripts/pythontex/pythontex_engines.py')
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex_engines.py706
1 files changed, 502 insertions, 204 deletions
diff --git a/Master/texmf-dist/scripts/pythontex/pythontex_engines.py b/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
index 83ae0acd50e..6bcbd1690db 100755
--- a/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
+++ b/Master/texmf-dist/scripts/pythontex/pythontex_engines.py
@@ -4,8 +4,8 @@ PythonTeX code engines.
Provides a class for managing the different languages/types of code
that may be executed. A class instance is created for each language/type of
-code. The class provides a method for assembling the scripts that are
-executed, combining user code with templates. It also creates the records
+code. The class provides a method for assembling the scripts that are
+executed, combining user code with templates. It also creates the records
needed to synchronize `stderr` with the document.
Each instance of the class is automatically added to the `engines_dict` upon
@@ -17,7 +17,7 @@ document (script for execution).
-Copyright (c) 2012-2014, Geoffrey M. Poore
+Copyright (c) 2012-2016, Geoffrey M. Poore
All rights reserved.
Licensed under the BSD 3-Clause License:
http://www.opensource.org/licenses/BSD-3-Clause
@@ -28,88 +28,109 @@ Licensed under the BSD 3-Clause License:
import os
import sys
import textwrap
+import re
from hashlib import sha1
from collections import OrderedDict, namedtuple
-interpreter_dict = {k:k for k in ('python', 'ruby', 'julia', 'octave')}
-# The {file} field needs to be replaced by itself, since the actual
+interpreter_dict = {k:k for k in ('python', 'ruby', 'julia', 'octave', 'bash', 'sage', 'rustc')}
+# 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
+# substitution for the interpreter should be done when the engine is
# initialized.
interpreter_dict['file'] = '{file}'
interpreter_dict['File'] = '{File}'
+interpreter_dict['workingdir'] = '{workingdir}'
engine_dict = {}
-CodeIndex = namedtuple('CodeIndex', ['input_file', 'command',
- 'line_int', 'lines_total',
+CodeIndex = namedtuple('CodeIndex', ['input_file', 'command',
+ 'line_int', 'lines_total',
'lines_user', 'lines_input',
'inline_count'])
class CodeEngine(object):
'''
- The base class that is used for defining language engines. Each command
+ The base class that is used for defining language engines. Each command
and environment family is based on an engine.
-
+
The class assembles the individual scripts that PythonTeX executes, using
- templates and user code. It also creates the records needed for
+ templates and user code. It also creates the records needed for
synchronizing `stderr` with the document.
'''
- def __init__(self, name, language, extension, command, template, wrapper,
- formatter, errors=None, warnings=None,
- linenumbers=None, lookbehind=False,
+ def __init__(self, name, language, extension, commands, template, wrapper,
+ formatter, sub=None, errors=None, warnings=None,
+ linenumbers=None, lookbehind=False,
console=False, startup=None, created=None):
# Save raw arguments so that they may be reused by subtypes
- self._rawargs = (name, language, extension, command, template, wrapper,
- formatter, errors, warnings,
+ self._rawargs = (name, language, extension, commands, template, wrapper,
+ formatter, sub, errors, warnings,
linenumbers, lookbehind, console, startup, created)
-
+
# Type check all strings, and make sure everything is Unicode
if sys.version_info[0] == 2:
- if (not isinstance(name, basestring) or
- not isinstance(language, basestring) or
- not isinstance(extension, basestring) or
- not isinstance(command, basestring) or
+ if (not isinstance(name, basestring) or
+ not isinstance(language, basestring) or
+ not isinstance(extension, basestring) or
not isinstance(template, basestring) or
not isinstance(wrapper, basestring) or
- not isinstance(formatter, basestring)):
+ not isinstance(formatter, basestring) or
+ not isinstance(sub, basestring)):
raise TypeError('CodeEngine needs string in initialization')
self.name = unicode(name)
self.language = unicode(language)
self.extension = unicode(extension)
- self.command = unicode(command)
self.template = unicode(template)
self.wrapper = unicode(wrapper)
self.formatter = unicode(formatter)
+ self.sub = unicode(sub)
else:
- if (not isinstance(name, str) or
- not isinstance(language, str) or
- not isinstance(extension, str) or
- not isinstance(command, str) or
+ if (not isinstance(name, str) or
+ not isinstance(language, str) or
+ not isinstance(extension, str) or
not isinstance(template, str) or
not isinstance(wrapper, str) or
- not isinstance(formatter, str)):
+ not isinstance(formatter, str) or
+ not isinstance(sub, str)):
raise TypeError('CodeEngine needs string in initialization')
self.name = name
self.language = language
self.extension = extension
- self.command = command
self.template = template
self.wrapper = wrapper
self.formatter = formatter
+ self.sub = sub
# Perform some additional formatting on some strings.
self.extension = self.extension.lstrip('.')
self.template = self._dedent(self.template)
self.wrapper = self._dedent(self.wrapper)
+ # Deal with commands
+ if sys.version_info.major == 2:
+ if isinstance(commands, basestring):
+ commands = [commands]
+ elif not isinstance(commands, list) and not isinstance(commands, tuple):
+ raise TypeError('CodeEngine needs "commands" to be a string, list, or tuple')
+ for c in commands:
+ if not isinstance(c, basestring):
+ raise TypeError('CodeEngine needs "commands" to contain strings')
+ commands = [unicode(c) for c in commands]
+ else:
+ if isinstance(commands, str):
+ commands = [commands]
+ elif not isinstance(commands, list) and not isinstance(commands, tuple):
+ raise TypeError('CodeEngine needs "commands" to be a string, list, or tuple')
+ for c in commands:
+ if not isinstance(c, str):
+ raise TypeError('CodeEngine needs "commands" to contain strings')
+ self.commands = commands
# Make sure formatter string ends with a newline
if not self.formatter.endswith('\n'):
self.formatter = self.formatter + '\n'
-
+
# Type check errors, warnings, and linenumbers
if errors is None:
errors = []
@@ -180,12 +201,12 @@ class CodeEngine(object):
if not isinstance(lookbehind, bool):
raise TypeError('CodeEngine needs "lookbehind" to be bool')
self.lookbehind = lookbehind
-
+
# Type check console
if not isinstance(console, bool):
raise TypeError('CodeEngine needs "console" to be bool')
self.console = console
-
+
# Type check startup
if startup is None:
startup = ''
@@ -203,7 +224,7 @@ class CodeEngine(object):
if not startup.endswith('\n'):
startup += '\n'
self.startup = self._dedent(startup)
-
+
# Type check created; make sure it is an iterable and contains Unicode
if created is None:
created = []
@@ -226,19 +247,24 @@ class CodeEngine(object):
if not isinstance(f, str):
raise TypeError('CodeEngine needs "created" to contain strings')
self.created = created
-
- # The base PythonTeX type does not support extend; it is used in
+
+ # The base PythonTeX type does not support extend; it is used in
# subtyping. But a dummy extend is needed to fill the extend field
# in templates, if it is provided.
self.extend = ''
-
+
# Create dummy variables for console
self.banner = ''
self.filename = ''
-
+
# Each type needs to add itself to a dict, for later access by name
self._register()
-
+
+ # Regex for working with `sub` commands and environments
+ # Generated if used
+ self.sub_field_re = None
+
+
def _dedent(self, s):
'''
Dedent and strip leading newlines
@@ -247,29 +273,29 @@ class CodeEngine(object):
while s.startswith('\n'):
s = s[1:]
return s
-
+
def _register(self):
'''
Add instance to a dict for later access by name
'''
engine_dict[self.name] = self
-
+
def customize(self, **kwargs):
'''
Customize the template on the fly.
-
- This provides customization based on command line arguments
+
+ This provides customization based on command line arguments
(`--interpreter`) and customization from the TeX side (imports from
- `__future__`). Ideally, this function should be restricted to this
- and similar cases. The custom code command and environment are
+ `__future__`). Ideally, this function should be restricted to this
+ and similar cases. The custom code command and environment are
insufficient for such cases, because the command is at a level above
- that of code and because of the requirement that imports from
+ that of code and because of the requirement that imports from
`__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)
+ self.commands = [c.format(**interpreter_dict) for c in self.commands]
# Take care of `__future__`
if self.language.startswith('python'):
if sys.version_info[0] == 2 and 'pyfuture' in kwargs:
@@ -317,7 +343,7 @@ class CodeEngine(object):
self.filename = kwargs['pyconfilename']
_hash = None
-
+
def get_hash(self):
'''
Return a hash of all vital type information (template, etc.). Create
@@ -328,7 +354,8 @@ class CodeEngine(object):
# the user, since a unique hash is all that's needed.
if self._hash is None:
hasher = sha1()
- hasher.update(self.command.encode('utf8'))
+ for c in self.commands:
+ hasher.update(c.encode('utf8'))
hasher.update(self.template.encode('utf8'))
hasher.update(self.wrapper.encode('utf8'))
hasher.update(self.formatter.encode('utf8'))
@@ -338,13 +365,13 @@ class CodeEngine(object):
hasher.update(self.filename.encode('utf8'))
self._hash = hasher.hexdigest()
return self._hash
-
+
def _process_future(self, code_list):
'''
- Go through a given list of code and extract all imports from
- `__future__`, so that they can be relocated to the beginning of the
+ Go through a given list of code and extract all imports from
+ `__future__`, so that they can be relocated to the beginning of the
script.
-
+
The approach isn't foolproof and doesn't support compound statements.
'''
done = False
@@ -355,8 +382,8 @@ class CodeEngine(object):
code = c.code.split('\n')
for l, line in enumerate(code):
# Detect __future__ imports
- if (line.startswith('from __future__') or
- line.startswith('import __future__') and
+ if (line.startswith('from __future__') or
+ line.startswith('import __future__') and
not in_triplequote):
changed = True
if ';' in line:
@@ -365,15 +392,15 @@ class CodeEngine(object):
future_imports.append(line)
code[l] = ''
# Ignore comments, empty lines, and lines with complete docstrings
- elif (line.startswith('\n') or line.startswith('#') or
+ elif (line.startswith('\n') or line.startswith('#') or
line.isspace() or
- ('"""' in line and line.count('"""')%2 == 0) or
+ ('"""' in line and line.count('"""')%2 == 0) or
("'''" in line and line.count("'''")%2 == 0)):
pass
# Detect if entering or leaving a docstring
elif line.count('"""')%2 == 1 or line.count("'''")%2 == 1:
in_triplequote = not in_triplequote
- # Stop looking for future imports as soon as a non-comment,
+ # Stop looking for future imports as soon as a non-comment,
# non-empty, non-docstring, non-future import line is found
elif not in_triplequote:
done = True
@@ -386,7 +413,7 @@ class CodeEngine(object):
return '\n'.join(future_imports)
else:
return ''
-
+
def _get_future(self, cc_list_begin, code_list):
'''
Process custom code and user code for imports from `__future__`
@@ -397,31 +424,31 @@ class CodeEngine(object):
return cc_future + '\n' + code_future
else:
return cc_future + code_future
-
- def get_script(self, encoding, utilspath, outputdir, workingdir,
+
+ 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
- numbers with document line numbers and user code line numbers in the
+ numbers with document line numbers and user code line numbers in the
event of errors or warnings.
'''
lines_total = 0
script = []
code_index = OrderedDict()
-
+
# Take care of future
if self.language.startswith('python'):
future = self._get_future(cc_list_begin, code_list)
else:
future = ''
-
+
# Split template into beginning and ending segments
try:
script_begin, script_end = self.template.split('{body}')
except:
raise ValueError('Template for ' + self.name + ' is missing {body}')
-
+
# Add beginning to script
if os.path.isabs(os.path.expanduser(os.path.normcase(workingdir))):
workingdir_full = workingdir
@@ -429,12 +456,12 @@ class CodeEngine(object):
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
+ # #### 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,
+ script_begin = script_begin.format(encoding=encoding, future=future,
utilspath=utilspath,
workingdir=os.path.expanduser(os.path.normcase(workingdir)),
Workingdir=workingdir_full,
@@ -446,7 +473,7 @@ class CodeEngine(object):
created_delim='=>PYTHONTEX:CREATED#')
script.append(script_begin)
lines_total += script_begin.count('\n')
-
+
# Prep wrapper
try:
wrapper_begin, wrapper_end = self.wrapper.split('{code}')
@@ -457,7 +484,7 @@ class CodeEngine(object):
# (and perhaps others) will use the line number from the NEXT
# line of code that is non-empty, not from the line of code where
# the error started. In these cases, it's important
- # to make sure that the line number is triggered immediately
+ # to make sure that the line number is triggered immediately
# after user code, so that the line number makes sense. Hence,
# we need to strip all whitespace from the part of the wrapper
# that follows user code. For symetry, we do the same for both
@@ -469,9 +496,9 @@ class CodeEngine(object):
wrapper_begin = wrapper_begin.replace('{stdoutdelim}', stdoutdelim).replace('{stderrdelim}', stderrdelim)
wrapper_begin_offset = wrapper_begin.count('\n')
wrapper_end_offset = wrapper_end.count('\n')
-
+
# Take care of custom code
- # Line counters must be reset for cc begin, code, and cc end, since
+ # Line counters must be reset for cc begin, code, and cc end, since
# all three are separate
lines_user = 0
inline_count = 0
@@ -483,7 +510,7 @@ class CodeEngine(object):
args=c.args_run,
instance=c.instance,
line=c.line))
-
+
# Actual code
lines_input = c.code.count('\n')
code_index[c.instance] = CodeIndex(c.input_file, c.command, c.line_int, lines_total, lines_user, lines_input, inline_count)
@@ -492,11 +519,11 @@ class CodeEngine(object):
inline_count += 1
lines_total += lines_input
lines_user += lines_input
-
+
# Wrapper after
script.append(wrapper_end)
lines_total += wrapper_end_offset
-
+
# Take care of user code
lines_user = 0
inline_count = 0
@@ -508,22 +535,33 @@ class CodeEngine(object):
args=c.args_run,
instance=c.instance,
line=c.line))
-
+
# Actual code
- lines_input = c.code.count('\n')
- 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
+ if c.command in ('s', 'sub'):
+ field_list = self.process_sub(c)
+ code = ''.join(self.sub.format(field_delim='=>PYTHONTEX:FIELD_DELIM#', field=field) for field in field_list)
+ lines_input = code.count('\n')
+ code_index[c.instance] = CodeIndex(c.input_file, c.command, c.line_int, lines_total, lines_user, lines_input, inline_count)
+ script.append(code)
+ # #### The traceback system will need to be redone to give
+ # better line numbers
+ lines_total += lines_input
+ lines_user += lines_input
else:
- script.append(c.code)
- lines_total += lines_input
- lines_user += lines_input
-
+ lines_input = c.code.count('\n')
+ 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:
+ script.append(c.code)
+ lines_total += lines_input
+ lines_user += lines_input
+
# Wrapper after
script.append(wrapper_end)
lines_total += wrapper_end_offset
-
+
# Take care of custom code
lines_user = 0
inline_count = 0
@@ -535,7 +573,7 @@ class CodeEngine(object):
args=c.args_run,
instance=c.instance,
line=c.line))
-
+
# Actual code
lines_input = c.code.count('\n')
code_index[c.instance] = CodeIndex(c.input_file, c.command, c.line_int, lines_total, lines_user, lines_input, inline_count)
@@ -544,32 +582,105 @@ class CodeEngine(object):
inline_count += 1
lines_total += lines_input
lines_user += lines_input
-
+
# Wrapper after
script.append(wrapper_end)
lines_total += wrapper_end_offset
-
+
# Finish script
script.append(script_end.format(dependencies_delim='=>PYTHONTEX:DEPENDENCIES#', created_delim='=>PYTHONTEX:CREATED#'))
-
+
return script, code_index
-
+ def process_sub(self, pytxcode):
+ '''
+ Take the code part of a `sub` command or environment, which is
+ essentially an interpolation string, and extract the replacement
+ fields. Process the replacement fields into a form suitable for
+ execution and process the string into a template into which the output
+ may be substituted.
+ '''
+ start = '!'
+ open_delim = '{'
+ close_delim = '}'
+ if self.sub_field_re is None:
+ field_pattern_list = []
+
+ # {s}: start, {o}: open_delim, {c}: close_delim
+ field_content_1_recursive = r'(?:[^{o}{c}\n]*|{o}R{c})+'
+ field_content_1_final_inner = r'[^{o}{c}\n]*'
+ field_1 = '{s}{o}(?!{o})' + field_content_1_recursive + '(?<!{c}){c}'
+ for n in range(5): # Want to allow 5 levels inside
+ field_1 = field_1.replace('R', field_content_1_recursive)
+ field_1 = field_1.replace('R', field_content_1_final_inner)
+ field_1 = field_1.format(s=re.escape(start), o=re.escape(open_delim), c=re.escape(close_delim))
+ field_pattern_list.append(field_1)
+
+ for n in range(2, 6+1): # Want to allow 5 levels inside
+ field_n = '{s}' + '{o}'*n + '(?!{o})F(?<!{c})' + '{c}'*n
+ field_n = field_n.replace('F', '(?:[^{o}{c}\n]*|{o}{{1,{n_minus}}}(?!{o})|{c}{{1,{n_minus}}}(?!{c}))+')
+ field_n = field_n.format(s=re.escape(start), o=re.escape(open_delim), c=re.escape(close_delim), n_minus=n-1)
+ field_pattern_list.append(field_n)
+
+ field = '|'.join(field_pattern_list)
+
+ escaped_start = '(?<!{s})(?:{s}{s})+(?={s}{o}|{o})'.format(s=re.escape(start), o=re.escape(open_delim))
+
+ pattern = '''
+ (?P<escaped>{es}) |
+ (?P<field>{f}) |
+ (?P<invalid>{so}) |
+ (?P<text_literal_start>{s}+) |
+ (?P<text_general>[^{s}]+)
+ '''.format(es=escaped_start, f=field, so=re.escape(start + open_delim), s=re.escape(start))
+ self.sub_field_re = re.compile(pattern, re.VERBOSE)
+
+ template_list = []
+ field_list = []
+ field_number = 0
+ for m in self.sub_field_re.finditer(pytxcode.code):
+ if m.lastgroup == 'escaped':
+ template_list.append(m.group().replace(start+start, start))
+ elif m.lastgroup == 'field':
+ template_list.append('{{{0}}}'.format(field_number))
+ field_list.append(m.group()[1:].lstrip(open_delim).rstrip(close_delim).strip())
+ field_number += 1
+ elif m.lastgroup.startswith('text'):
+ template_list.append(m.group().replace('{', '{{').replace('}', '}}'))
+ else:
+ msg = '''\
+ * PythonTeX error:
+ Invalid "sub" command or environment. Invalid replacement fields.
+ {0}on or after line {1}
+ '''.format(pytxcode.input_file + ': ' if pytxcode.input_file else '', pytxcode.line)
+ msg = textwrap.dedent(msg)
+ sys.exit(msg)
+
+ pytxcode.sub_template = ''.join(template_list)
+
+ return field_list
+
+
+
+
+
+
+
class SubCodeEngine(CodeEngine):
'''
Create Engine instances that inherit from existing instances.
'''
- def __init__(self, base, name, language=None, extension=None, command=None,
- template=None, wrapper=None, formatter=None, errors=None,
- warnings=None, linenumbers=None, lookbehind=False,
+ def __init__(self, base, name, language=None, extension=None, commands=None,
+ template=None, wrapper=None, formatter=None, sub=None,
+ errors=None, warnings=None, linenumbers=None, lookbehind=False,
console=None, created=None, startup=None, extend=None):
-
- self._rawargs = (name, language, extension, command, template, wrapper,
- formatter, errors, warnings,
+
+ self._rawargs = (name, language, extension, commands, template, wrapper,
+ formatter, sub, errors, warnings,
linenumbers, lookbehind, console, startup, created)
-
+
base_rawargs = engine_dict[base]._rawargs
args = []
for n, arg in enumerate(self._rawargs):
@@ -577,11 +688,11 @@ class SubCodeEngine(CodeEngine):
args.append(base_rawargs[n])
else:
args.append(arg)
-
+
CodeEngine.__init__(self, *args)
-
+
self.extend = engine_dict[base].extend
-
+
if extend is not None:
if sys.version_info[0] == 2:
if not isinstance(extend, basestring):
@@ -601,15 +712,15 @@ class PythonConsoleEngine(CodeEngine):
'''
This uses the Engine class to store information needed for emulating
Python interactive consoles.
-
- In the current form, it isn't used as a real engine, but rather as a
- convenient storage class that keeps the treatment of all languages/code
+
+ In the current form, it isn't used as a real engine, but rather as a
+ convenient storage class that keeps the treatment of all languages/code
types uniform.
'''
def __init__(self, name, startup=None):
- CodeEngine.__init__(self, name=name, language='python',
- extension='', command='', template='',
- wrapper='', formatter='', errors=None,
+ CodeEngine.__init__(self, name=name, language='python',
+ extension='', commands='', template='',
+ wrapper='', formatter='', sub='', errors=None,
warnings=None, linenumbers=None, lookbehind=False,
console=True, startup=startup, created=None)
@@ -618,13 +729,13 @@ class PythonConsoleEngine(CodeEngine):
python_template = '''
# -*- coding: {encoding} -*-
-
+
{future}
import os
import sys
import codecs
-
+
if '--interactive' not in sys.argv[1:]:
if sys.version_info[0] == 2:
sys.stdout = codecs.getwriter('{encoding}')(sys.stdout, 'strict')
@@ -632,12 +743,12 @@ python_template = '''
else:
sys.stdout = codecs.getwriter('{encoding}')(sys.stdout.buffer, 'strict')
sys.stderr = codecs.getwriter('{encoding}')(sys.stderr.buffer, 'strict')
-
+
if '{utilspath}' and '{utilspath}' not in sys.path:
- sys.path.append('{utilspath}')
+ sys.path.append('{utilspath}')
from pythontex_utils import PythonTeXUtils
pytex = PythonTeXUtils()
-
+
pytex.docdir = os.getcwd()
if os.path.isdir('{workingdir}'):
os.chdir('{workingdir}')
@@ -648,14 +759,14 @@ python_template = '''
sys.exit('Cannot find directory {workingdir}')
if pytex.docdir not in sys.path:
sys.path.append(pytex.docdir)
-
+
{extend}
-
+
pytex.id = '{family}_{session}_{restart}'
pytex.family = '{family}'
pytex.session = '{session}'
pytex.restart = '{restart}'
-
+
{body}
pytex.cleanup()
@@ -667,25 +778,34 @@ python_wrapper = '''
pytex.args = '{args}'
pytex.instance = '{instance}'
pytex.line = '{line}'
-
+
print('{stdoutdelim}')
sys.stderr.write('{stderrdelim}\\n')
pytex.before()
-
+
{code}
-
+
pytex.after()
'''
+python_sub = '''print('{field_delim}')\nprint({field})\n'''
+
CodeEngine('python', 'python', '.py', '{python} {file}.py',
- python_template, python_wrapper, 'print(pytex.formatter({code}))',
- 'Error:', 'Warning:', ['line {number}', ':{number}:'])
+ python_template, python_wrapper, 'print(pytex.formatter({code}))',
+ python_sub, 'Error:', 'Warning:', ['line {number}', ':{number}:'])
SubCodeEngine('python', 'py')
SubCodeEngine('python', 'pylab', extend='from pylab import *')
+
+SubCodeEngine('python', 'sage', language='sage', extension='.sage',
+ template=python_template.replace('{future}', ''),
+ extend = 'pytex.formatter = latex',
+ commands='{sage} {file}.sage')
+
+
sympy_extend = '''
from sympy import *
pytex.set_formatter('sympy_latex')
@@ -707,15 +827,15 @@ PythonConsoleEngine('sympycon', startup='from sympy import *')
ruby_template = '''
# -*- coding: {encoding} -*-
-
+
unless ARGV.include?('--interactive')
$stdout.set_encoding('{encoding}')
$stderr.set_encoding('{encoding}')
end
-
+
class RubyTeXUtils
- attr_accessor :id, :family, :session, :restart,
- :command, :context, :args,
+ attr_accessor :id, :family, :session, :restart,
+ :command, :context, :args,
:instance, :line, :dependencies, :created,
:docdir, :_context_raw
def initialize
@@ -770,11 +890,11 @@ ruby_template = '''
if @created
@created.each {{ |x| puts x }}
end
- end
+ end
end
-
+
rbtex = RubyTeXUtils.new
-
+
rbtex.docdir = Dir.pwd
if File.directory?('{workingdir}')
Dir.chdir('{workingdir}')
@@ -783,14 +903,14 @@ ruby_template = '''
abort('Cannot change to directory {workingdir}')
end
$LOAD_PATH.push(rbtex.docdir) unless $LOAD_PATH.include?(rbtex.docdir)
-
+
{extend}
-
+
rbtex.id = '{family}_{session}_{restart}'
rbtex.family = '{family}'
rbtex.session = '{session}'
rbtex.restart = '{restart}'
-
+
{body}
rbtex.cleanup
@@ -802,18 +922,21 @@ ruby_wrapper = '''
rbtex.args = '{args}'
rbtex.instance = '{instance}'
rbtex.line = '{line}'
-
+
puts '{stdoutdelim}'
$stderr.puts '{stderrdelim}'
rbtex.before
-
+
{code}
-
+
rbtex.after
'''
-CodeEngine('ruby', 'ruby', '.rb', '{ruby} {file}.rb', ruby_template,
- ruby_wrapper, 'puts rbtex.formatter({code})',
+ruby_sub = '''puts '{field_delim}'\nputs {field}\n'''
+
+
+CodeEngine('ruby', 'ruby', '.rb', '{ruby} {file}.rb', ruby_template,
+ ruby_wrapper, 'puts rbtex.formatter({code})', ruby_sub,
['Error)', '(Errno', 'error'], 'warning:', ':{number}:')
SubCodeEngine('ruby', 'rb')
@@ -823,26 +946,26 @@ SubCodeEngine('ruby', 'rb')
julia_template = '''
# -*- coding: UTF-8 -*-
-
+
# Currently, Julia only supports UTF-8
# So can't set stdout and stderr encoding
-
+
type JuliaTeXUtils
- id::String
- family::String
- session::String
- restart::String
- command::String
+ id::AbstractString
+ family::AbstractString
+ session::AbstractString
+ restart::AbstractString
+ command::AbstractString
context::Dict
- args::String
- instance::String
- line::String
-
- _dependencies::Array{{String}}
- _created::Array{{String}}
- docdir::String
- _context_raw::String
-
+ args::AbstractString
+ instance::AbstractString
+ line::AbstractString
+
+ _dependencies::Array{{AbstractString}}
+ _created::Array{{AbstractString}}
+ docdir::AbstractString
+ _context_raw::AbstractString
+
formatter::Function
before::Function
after::Function
@@ -854,26 +977,26 @@ julia_template = '''
pt_to_mm::Function
pt_to_bp::Function
cleanup::Function
-
+
self::JuliaTeXUtils
-
+
function JuliaTeXUtils()
self = new()
self.self = self
- self._dependencies = Array(String, 0)
- self._created = Array(String, 0)
+ self._dependencies = Array(AbstractString, 0)
+ self._created = Array(AbstractString, 0)
self._context_raw = ""
-
+
function formatter(expr)
string(expr)
end
self.formatter = formatter
-
+
function null()
end
self.before = null
self.after = null
-
+
function add_dependencies(files...)
for file in files
push!(self._dependencies, file)
@@ -886,17 +1009,17 @@ julia_template = '''
end
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 = Dict{{Any, Any}}([ 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 isa(expr, AbstractString)
if sizeof(expr) > 2 && expr[end-1:end] == "pt"
expr = expr[1:end-2]
end
@@ -906,22 +1029,22 @@ julia_template = '''
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}")
for f in self._dependencies
@@ -933,13 +1056,13 @@ julia_template = '''
end
end
self.cleanup = cleanup
-
+
return self
end
end
-
+
jltex = JuliaTeXUtils()
-
+
jltex.docdir = pwd()
try
cd("{workingdir}")
@@ -950,17 +1073,17 @@ julia_template = '''
end
if !(in(jltex.docdir, LOAD_PATH))
push!(LOAD_PATH, jltex.docdir)
- end
-
+ end
+
{extend}
-
+
jltex.id = "{family}_{session}_{restart}"
jltex.family = "{family}"
jltex.session = "{session}"
jltex.restart = "{restart}"
-
+
{body}
-
+
jltex.cleanup()
'''
@@ -968,29 +1091,34 @@ julia_wrapper = '''
jltex.command = "{command}"
jltex.set_context("{context}")
jltex.args = "{args}"
- jltex.instance = "{instance}"
+ jltex.instance = "{instance}"
jltex.line = "{line}"
-
+
println("{stdoutdelim}")
write(STDERR, "{stderrdelim}\\n")
- jltex.before()
-
+ jltex.before()
+
{code}
-
+
jltex.after()
'''
-CodeEngine('julia', 'julia', '.jl', '{julia} "{file}.jl"', julia_template,
- julia_wrapper, 'println(jltex.formatter({code}))',
+julia_sub = '''println("{field_delim}")\nprintln({field})\n'''
+
+
+CodeEngine('julia', 'julia', '.jl', '{julia} "{file}.jl"', julia_template,
+ julia_wrapper, 'println(jltex.formatter({code}))', julia_sub,
'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
@@ -1002,30 +1130,30 @@ octave_template = '''
error("Could not find directory {workingdir}");
end
end
- if find_dir_in_path(octavetex.docdir)
+ if dir_in_loadpath(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)
@@ -1033,7 +1161,7 @@ octave_template = '''
end
end
octavetex.add_dependencies = @(varargin) octavetex_add_dependencies(varargin{{:}});
-
+
function octavetex_add_created(varargin)
global octavetex;
for i = 1:length(varargin)
@@ -1041,7 +1169,7 @@ octave_template = '''
end
end
octavetex.add_created = @(varargin) octavetex_add_created(varargin{{:}});
-
+
function octavetex_set_context(argin)
global octavetex;
if ~strcmp(argin, octavetex._context_raw)
@@ -1058,7 +1186,7 @@ octave_template = '''
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'
@@ -1071,22 +1199,22 @@ octave_template = '''
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"));
@@ -1096,18 +1224,18 @@ octave_template = '''
fprintf(strcat('{created_delim}', "\\n"));
for i = 1:length(octavetex.created)
fprintf(strcat(octavetex.created{{i}}, "\\n"));
- end
+ end
end
octavetex.cleanup = @() octavetex_cleanup();
-
+
octavetex.id = '{family}_{session}_{restart}';
octavetex.family = '{family}';
octavetex.session = '{session}';
octavetex.restart = '{restart}';
-
+
{body}
- octavetex.cleanup()
+ octavetex.cleanup()
'''
octave_wrapper = '''
@@ -1116,17 +1244,187 @@ octave_wrapper = '''
octavetex.args = '{args}';
octavetex.instance = '{instance}';
octavetex.line = '{line}';
-
- octavetex.before()
-
+
+ octavetex.before()
+
fprintf(strcat('{stdoutdelim}', "\\n"));
fprintf(stderr, strcat('{stderrdelim}', "\\n"));
{code}
-
+
octavetex.after()
'''
+octave_sub = '''disp("{field_delim}")\ndisp({field})\n'''
+
CodeEngine('octave', 'octave', '.m',
- '{octave} -q "{File}.m"',
- octave_template, octave_wrapper, 'disp({code})',
+ '{octave} -q "{File}.m"',
+ octave_template, octave_wrapper, 'disp({code})', octave_sub,
'error', 'warning', 'line {number}')
+
+bash_template = '''
+ cd "{workingdir}"
+ {body}
+ echo "{dependencies_delim}"
+ echo "{created_delim}"
+ '''
+
+bash_wrapper = '''
+ echo "{stdoutdelim}"
+ >&2 echo "{stderrdelim}"
+ {code}
+ '''
+
+bash_sub = '''echo "{field_delim}"\necho {field}\n'''
+
+CodeEngine('bash', 'bash', '.sh',
+ '{bash} "{file}.sh"',
+ bash_template, bash_wrapper, '{code}', bash_sub,
+ ['error', 'Error'], ['warning', 'Warning'],
+ 'line {number}')
+
+
+rust_template = '''
+ // -*- coding: utf-8 -*-
+ #![allow(dead_code)]
+ #![allow(unused_imports)]
+
+
+ mod rust_tex_utils {{
+ use std::fmt;
+ use std::collections;
+ use std::io::prelude::*;
+
+ pub struct RustTeXUtils {{
+ _formatter: Box<FnMut(&fmt::Display) -> String>,
+ _before: Box<FnMut()>,
+ _after: Box<FnMut()>,
+ pub family: &'static str,
+ pub session: &'static str,
+ pub restart: &'static str,
+ pub dependencies: Vec<String>,
+ pub created: Vec<String>,
+ pub command: &'static str,
+ pub context: collections::HashMap<&'static str, &'static str>,
+ pub args: collections::HashMap<&'static str, &'static str>,
+ pub instance: &'static str,
+ pub line: &'static str,
+ }}
+
+ impl RustTeXUtils {{
+ pub fn new() -> Self {{
+ RustTeXUtils {{
+ _formatter: Box::new(|x: &fmt::Display| format!("{{}}", x)),
+ _before: Box::new(|| {{}}),
+ _after: Box::new(|| {{}}),
+ family: "{family}",
+ session: "{session}",
+ restart: "{restart}",
+ dependencies: Vec::new(),
+ created: Vec::new(),
+ command: "",
+ context: collections::HashMap::new(),
+ args: collections::HashMap::new(),
+ instance: "",
+ line: "",
+ }}
+ }}
+
+
+ pub fn formatter<A: fmt::Display>(&mut self, x: A) -> String {{
+ (*self._formatter)(&x)
+ }}
+ pub fn set_formatter<F: FnMut(&fmt::Display) -> String + 'static>(&mut self, f: F) {{
+ self._formatter = Box::new(f);
+ }}
+
+ pub fn before(&mut self) {{
+ (*self._before)();
+ }}
+ pub fn set_before<F: FnMut() + 'static>(&mut self, f: F) {{
+ self._before = Box::new(f);
+ }}
+
+ pub fn after(&mut self) {{
+ (*self._after)();
+ }}
+ pub fn set_after<F: FnMut() + 'static>(&mut self, f: F) {{
+ self._after = Box::new(f);
+ }}
+
+ pub fn add_dependencies<SS: IntoIterator>(&mut self, deps: SS) where SS::Item: Into<String> {{
+ self.dependencies.append(&mut deps.into_iter().map(|x| x.into()).collect());
+ }}
+
+ pub fn add_created<SS: IntoIterator>(&mut self, crts: SS) where SS::Item: Into<String> {{
+ self.created.append(&mut crts.into_iter().map(|x| x.into()).collect());
+ }}
+
+ pub fn cleanup(self) {{
+ println!("{{}}", "{dependencies_delim}");
+ for x in self.dependencies {{
+ println!("{{}}", x);
+ }}
+ println!("{{}}", "{created_delim}");
+ for x in self.created {{
+ println!("{{}}", x);
+ }}
+ }}
+
+ pub fn setup_wrapper(&mut self, cmd: &'static str, cxt: &'static str, ags: &'static str, ist: &'static str, lne: &'static str) {{
+ fn parse_map(kvs: &'static str) -> collections::HashMap<&'static str, &'static str> {{
+ kvs.split(',').filter(|s| !s.is_empty()).map(|kv| {{
+ let (k, v) = kv.split_at(kv.find('=').expect(&format!("Error parsing supposed key-value pair ({{}})", kv)));
+ (k.trim(), v[1..].trim())
+ }}).collect()
+ }}
+ self.command = cmd;
+ self.context = parse_map(cxt);
+ self.args = parse_map(ags);
+ self.instance = ist;
+ self.line = lne;
+ }}
+ }}
+ }}
+
+
+ use std::{{io, fmt, env, path, ffi, collections}};
+ use std::io::prelude::*;
+
+
+ #[allow(unused_mut)]
+ fn main() {{
+ let mut rstex = rust_tex_utils::RustTeXUtils::new();
+ if env::set_current_dir(ffi::OsString::from("{workingdir}".to_string())).is_err() && env::args().all(|x| x != "--manual") {{
+ panic!("Could not change to the specified working directory ({workingdir})");
+ }}
+
+ {extend}
+
+ {body}
+
+ rstex.cleanup();
+ }}
+ '''
+
+rust_wrapper = '''
+ rstex.setup_wrapper("{command}", "{context}", "{args}", "{instance}", "{line}");
+ println!("{stdoutdelim}");
+ writeln!(io::stderr(), "{stderrdelim}").unwrap();
+ rstex.before();
+
+ {code}
+
+ rstex.after();
+ '''
+
+rust_sub = '''println!("{field_delim}");\nprintln!("{{}}", {field});\n'''
+
+CodeEngine('rust', 'rust', '.rs',
+ # The full script name has to be used in order to make Windows and Unix behave nicely
+ # together when naming executables. Despite appearances, using `.exe` works on Unix too.
+ ['{rustc} --crate-type bin -o {File}.exe -L {workingdir} {file}.rs', '{File}.exe'],
+ rust_template, rust_wrapper, 'println!("{{}}", rstex.formatter({code}));', rust_sub,
+ errors='error:', warnings='warning:', linenumbers='.rs:{number}',
+ created='{File}.exe')
+
+SubCodeEngine('rust', 'rs')