summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts/pythontex/pythontex2.py
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/scripts/pythontex/pythontex2.py')
-rwxr-xr-xMaster/texmf-dist/scripts/pythontex/pythontex2.py700
1 files changed, 498 insertions, 202 deletions
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)