summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/pythontex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2014-07-14 22:39:29 +0000
committerKarl Berry <karl@freefriends.org>2014-07-14 22:39:29 +0000
commit91e62921c8f6d17fb3a1b701c0b5f99cfeadd408 (patch)
tree8ba1865818a69de5220113ae92b7c54f849fb5f3 /Master/texmf-dist/scripts/pythontex
parent5dae7a4d678252feb9eda590f0c6a547a5bcc4d9 (diff)
pythontex (14jul14)
git-svn-id: svn://tug.org/texlive/trunk@34605 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts/pythontex')
-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
11 files changed, 2064 insertions, 976 deletions
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()