X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=lib%2Fscripts%2Flyxpreview_tools.py;h=7783fe2b7ddde61529d3d5307642ebb828887870;hb=e4065cc1b1394c61108735611e0ebf02b12e6bbe;hp=b24f9721559e9db300c0bc733705236d9851b11a;hpb=bb55234cac764d0465cdc4cc10a40d9b8a6cb5ba;p=lyx.git diff --git a/lib/scripts/lyxpreview_tools.py b/lib/scripts/lyxpreview_tools.py index b24f972155..7783fe2b7d 100644 --- a/lib/scripts/lyxpreview_tools.py +++ b/lib/scripts/lyxpreview_tools.py @@ -1,4 +1,3 @@ -#! /usr/bin/env python # file lyxpreview_tools.py # This file is part of LyX, the document processor. @@ -11,19 +10,20 @@ # Paul A. Rubin, rubin@msu.edu. # A repository of the following functions, used by the lyxpreview2xyz scripts. -# copyfileobj, error, find_exe, find_exe_or_terminate, make_texcolor, mkstemp, -# progress, run_command, warning +# copyfileobj, error, find_exe, find_exe_or_terminate, make_texcolor, +# progress, run_command, run_latex, warning # Requires python 2.4 or later (subprocess module). -import os, re, string, subprocess, sys, tempfile +import os, re, subprocess, sys, tempfile # Control the output to stdout debug = False verbose = False -# Known flavors of latex +# Known flavors of latex and bibtex +bibtex_commands = ("bibtex", "bibtex8", "biber") latex_commands = ("latex", "pplatex", "platex", "latex2e") pdflatex_commands = ("pdflatex", "xelatex", "lualatex") @@ -35,6 +35,7 @@ path = os.environ["PATH"].split(os.pathsep) extlist = [''] if "PATHEXT" in os.environ: extlist += os.environ["PATHEXT"].split(os.pathsep) +extlist.append('.py') use_win32_modules = 0 if os.name == "nt": @@ -71,24 +72,25 @@ def error(message): def make_texcolor(hexcolor, graphics): # Test that the input string contains 6 hexadecimal chars. - hexcolor_re = re.compile("^[0-9a-fA-F]{6}$") + hexcolor_re = re.compile(b"^[0-9a-fA-F]{6}$") if not hexcolor_re.match(hexcolor): error("Cannot convert color '%s'" % hexcolor) - red = float(string.atoi(hexcolor[0:2], 16)) / 255.0 - green = float(string.atoi(hexcolor[2:4], 16)) / 255.0 - blue = float(string.atoi(hexcolor[4:6], 16)) / 255.0 + red = float(int(hexcolor[0:2], 16)) / 255.0 + green = float(int(hexcolor[2:4], 16)) / 255.0 + blue = float(int(hexcolor[4:6], 16)) / 255.0 if graphics: - return "%f,%f,%f" % (red, green, blue) + return b"%f,%f,%f" % (red, green, blue) else: - return "rgb %f %f %f" % (red, green, blue) + return b"rgb %f %f %f" % (red, green, blue) def find_exe(candidates): global extlist, path - for prog in candidates: + for command in candidates: + prog = command.split()[0] for directory in path: for ext in extlist: full_path = os.path.join(directory, prog + ext) @@ -97,7 +99,10 @@ def find_exe(candidates): # have found it). Return just the basename to avoid # problems when the path to the executable contains # spaces. - return os.path.basename(full_path) + if full_path.lower().endswith('.py'): + return command.replace(prog, '"%s" "%s"' + % (sys.executable, full_path)) + return command return None @@ -105,15 +110,26 @@ def find_exe(candidates): def find_exe_or_terminate(candidates): exe = find_exe(candidates) if exe == None: - error("Unable to find executable from '%s'" % string.join(candidates)) + error("Unable to find executable from '%s'" % " ".join(candidates)) return exe -def run_command_popen(cmd): - pipe = subprocess.Popen(cmd, shell=True, close_fds=True, stdin=subprocess.PIPE, \ - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - cmd_stdout = pipe.communicate()[0] +def run_command_popen(cmd, stderr2stdout): + if os.name == 'nt': + unix = False + else: + unix = True + if stderr2stdout: + pipe = subprocess.Popen(cmd, shell=unix, close_fds=unix, stdin=subprocess.PIPE, \ + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + cmd_stdout = pipe.communicate()[0] + else: + pipe = subprocess.Popen(cmd, shell=unix, close_fds=unix, stdin=subprocess.PIPE, \ + stdout=subprocess.PIPE, universal_newlines=True) + (cmd_stdout, cmd_stderr) = pipe.communicate() + if cmd_stderr: + sys.stderr.write(cmd_stderr) cmd_status = pipe.returncode global debug @@ -152,7 +168,7 @@ def run_command_win32(cmd): if hr != winerror.ERROR_IO_PENDING: data = data + buffer - except pywintypes.error, e: + except pywintypes.error as e: if e.args[0] != winerror.ERROR_BROKEN_PIPE: error = 1 break @@ -172,22 +188,12 @@ def run_command_win32(cmd): return 0, data -def run_command(cmd): +def run_command(cmd, stderr2stdout = True): progress("Running %s" % cmd) if use_win32_modules: return run_command_win32(cmd) else: - return run_command_popen(cmd) - - -def get_version_info(): - version_re = re.compile("([0-9])\.([0-9])") - - match = version_re.match(sys.version) - if match == None: - error("Unable to extract version info from 'sys.version'") - - return string.atoi(match.group(1)), string.atoi(match.group(2)) + return run_command_popen(cmd, stderr2stdout) def copyfileobj(fsrc, fdst, rewind=0, length=16*1024): @@ -203,76 +209,43 @@ def copyfileobj(fsrc, fdst, rewind=0, length=16*1024): fdst.write(buf) -class TempFile: - """clone of tempfile.TemporaryFile to use with python < 2.0.""" - # Cache the unlinker so we don't get spurious errors at shutdown - # when the module-level "os" is None'd out. Note that this must - # be referenced as self.unlink, because the name TempFile - # may also get None'd out before __del__ is called. - unlink = os.unlink - - def __init__(self): - self.filename = tempfile.mktemp() - self.file = open(self.filename,"w+b") - self.close_called = 0 - - def close(self): - if not self.close_called: - self.close_called = 1 - self.file.close() - self.unlink(self.filename) - - def __del__(self): - self.close() - - def read(self, size = -1): - return self.file.read(size) - - def write(self, line): - return self.file.write(line) - - def seek(self, offset): - return self.file.seek(offset) - - def flush(self): - return self.file.flush() - - -def mkstemp(): - """create a secure temporary file and return its object-like file""" - major, minor = get_version_info() - - if major >= 2 and minor >= 0: - return tempfile.TemporaryFile() - else: - return TempFile() - def write_metrics_info(metrics_info, metrics_file): metrics = open(metrics_file, 'w') for metric in metrics_info: metrics.write("Snippet %s %f\n" % metric) metrics.close() + # Reads a .tex files and create an identical file but only with # pages whose index is in pages_to_keep def filter_pages(source_path, destination_path, pages_to_keep): - source_file = open(source_path, "r") - destination_file = open(destination_path, "w") + def_re = re.compile(b"(\\\\newcommandx|\\\\renewcommandx|\\\\global\\\\long\\\\def)(\\[a-zA-Z]+)(.+)") + source_file = open(source_path, "rb") + destination_file = open(destination_path, "wb") page_index = 0 skip_page = False + macros = [] for line in source_file: # We found a new page - if line.startswith("\\begin{preview}"): + if line.startswith(b"\\begin{preview}"): page_index += 1 # If the page index isn't in pages_to_keep we don't copy it skip_page = page_index not in pages_to_keep if not skip_page: + match = def_re.match(line) + if match != None: + definecmd = match.group(1) + macroname = match.group(2) + if not macroname in macros: + macros.append(macroname) + if definecmd == b"\\renewcommandx": + line = line.replace(definecmd, b"\\newcommandx") destination_file.write(line) # End of a page, we reset the skip_page bool - if line.startswith("\\end{preview}"): + if line.startswith(b"\\end{preview}"): skip_page = False destination_file.close() @@ -301,3 +274,85 @@ def join_metrics_and_rename(original_metrics, new_metrics, new_page_indexes, ori original_metrics[legacy_index] = (index, metric) else: original_metrics.insert(legacy_index, (index, metric)) + + +def run_latex(latex, latex_file, bibtex = None): + # Run latex + latex_status, latex_stdout = run_tex(latex, latex_file) + + if bibtex is None: + return latex_status, latex_stdout + + # The aux and log output file names + aux_file = latex_file_re.sub(".aux", latex_file) + log_file = latex_file_re.sub(".log", latex_file) + + # Run bibtex/latex if necessary + progress("Checking if a bibtex run is necessary") + if string_in_file(r"\bibdata", aux_file): + bibtex_status, bibtex_stdout = run_tex(bibtex, aux_file) + latex_status, latex_stdout = run_tex(latex, latex_file) + # Rerun latex if necessary + progress("Checking if a latex rerun is necessary") + if string_in_file("Warning: Citation", log_file): + latex_status, latex_stdout = run_tex(latex, latex_file) + + return latex_status, latex_stdout + + +def run_tex(tex, tex_file): + tex_call = '%s "%s"' % (tex, tex_file) + + tex_status, tex_stdout = run_command(tex_call) + if tex_status: + progress("Warning: %s had problems compiling %s" \ + % (os.path.basename(tex), tex_file)) + return tex_status, tex_stdout + + +def string_in_file(string, infile): + if not os.path.isfile(infile): + return False + f = open(infile, 'rb') + for line in f.readlines(): + if string.encode() in line: + f.close() + return True + f.close() + return False + + +# Returns a list of indexes of pages giving errors extracted from the latex log +def check_latex_log(log_file): + + error_re = re.compile(b"^! ") + snippet_re = re.compile(b"^Preview: Snippet ") + data_re = re.compile(b"([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)") + + found_error = False + error_pages = [] + + try: + for line in open(log_file, 'rb').readlines(): + if not found_error: + match = error_re.match(line) + if match != None: + found_error = True + continue + else: + match = snippet_re.match(line) + if match == None: + continue + + found_error = False + match = data_re.search(line) + if match == None: + error("Unexpected data in %s\n%s" % (log_file, line)) + + error_pages.append(int(match.group(1))) + + except: + warning('check_latex_log: Unable to open "%s"' % log_file) + warning(repr(sys.exc_info()[0]) + ',' + repr(sys.exc_info()[1])) + + return error_pages