Better error and progress messages for lyxpreview.

Introduce --debug and --verbose flags to control output. Also, use the
python subprocess module to capture stderr from external commands, thus
bumping the python requirement to version 2.4 or later.

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@39660 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Julien Rioux 2011-09-11 18:23:29 +00:00
parent 81420e405f
commit bb55234cac
3 changed files with 98 additions and 31 deletions

View File

@ -36,6 +36,7 @@
# the images correctly on the screen. # the images correctly on the screen.
# The script uses several external programs and files: # The script uses several external programs and files:
# * python 2.4 or later (subprocess module);
# * A latex executable; # * A latex executable;
# * preview.sty; # * preview.sty;
# * dvips; # * dvips;
@ -76,7 +77,7 @@ import glob, os, pipes, re, string, sys
from lyxpreview_tools import copyfileobj, error, filter_pages, find_exe, \ from lyxpreview_tools import copyfileobj, error, filter_pages, find_exe, \
find_exe_or_terminate, join_metrics_and_rename, latex_commands, \ find_exe_or_terminate, join_metrics_and_rename, latex_commands, \
latex_file_re, make_texcolor, mkstemp, run_command, warning, \ latex_file_re, make_texcolor, mkstemp, progress, run_command, warning, \
write_metrics_info write_metrics_info
@ -277,7 +278,7 @@ def legacy_conversion(argv, skipMetrics = False):
latex_call = '%s "%s"' % (latex, latex_file) latex_call = '%s "%s"' % (latex, latex_file)
latex_status, latex_stdout = run_command(latex_call) latex_status, latex_stdout = run_command(latex_call)
if latex_status != None: if latex_status:
warning("%s had problems compiling %s" \ warning("%s had problems compiling %s" \
% (os.path.basename(latex), latex_file)) % (os.path.basename(latex), latex_file))
@ -301,6 +302,9 @@ def legacy_conversion_pdflatex(latex_file, failed_pages, legacy_metrics, gs,
# pdflatex call # pdflatex call
pdflatex_call = '%s "%s"' % (pdflatex, pdf_latex_file) pdflatex_call = '%s "%s"' % (pdflatex, pdf_latex_file)
pdflatex_status, pdflatex_stdout = run_command(pdflatex_call) pdflatex_status, pdflatex_stdout = run_command(pdflatex_call)
if pdflatex_status:
warning("%s had problems compiling %s" \
% (os.path.basename(pdflatex), pdf_latex_file))
pdf_file = latex_file_re.sub(".pdf", pdf_latex_file) pdf_file = latex_file_re.sub(".pdf", pdf_latex_file)
@ -312,7 +316,7 @@ def legacy_conversion_pdflatex(latex_file, failed_pages, legacy_metrics, gs,
% (gs, gs_device, latex_file_re.sub("", pdf_latex_file), \ % (gs, gs_device, latex_file_re.sub("", pdf_latex_file), \
gs_ext, alpha, alpha, resolution, pdf_file) gs_ext, alpha, alpha, resolution, pdf_file)
gs_status, gs_stdout = run_command(gs_call) gs_status, gs_stdout = run_command(gs_call)
if gs_status != None: if gs_status:
# Give up! # Give up!
warning("Some pages failed with all the possible routes") warning("Some pages failed with all the possible routes")
else: else:
@ -343,7 +347,7 @@ def legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics = False)
dvips_failed = False dvips_failed = False
dvips_status, dvips_stdout = run_command(dvips_call) dvips_status, dvips_stdout = run_command(dvips_call)
if dvips_status != None: if dvips_status:
warning('Failed: %s %s ... looking for PDF' \ warning('Failed: %s %s ... looking for PDF' \
% (os.path.basename(dvips), dvi_file)) % (os.path.basename(dvips), dvi_file))
dvips_failed = True dvips_failed = True
@ -381,7 +385,7 @@ def legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics = False)
gs_ext, alpha, alpha, resolution, pdf_file) gs_ext, alpha, alpha, resolution, pdf_file)
gs_status, gs_stdout = run_command(gs_call) gs_status, gs_stdout = run_command(gs_call)
if gs_status != None: if gs_status:
error("Failed: %s %s" % (os.path.basename(gs), ps_file)) error("Failed: %s %s" % (os.path.basename(gs), ps_file))
else: else:
# Model for calling gs on each file # Model for calling gs on each file
@ -400,9 +404,11 @@ def legacy_conversion_step2(latex_file, dpi, output_format, skipMetrics = False)
# Call GhostScript for each file # Call GhostScript for each file
for file in ps_files: for file in ps_files:
i = i + 1 i = i + 1
progress("Processing page %s, file %s" % (i, file))
gs_status, gs_stdout = run_command(gs_call % (i, file)) gs_status, gs_stdout = run_command(gs_call % (i, file))
if gs_status != None: if gs_status:
# gs failed, keep track of this # gs failed, keep track of this
warning("Ghostscript failed on page %s, file %s" % (i, file))
failed_pages.append(i) failed_pages.append(i)
# Pass failed pages to pdflatex # Pass failed pages to pdflatex

View File

@ -16,6 +16,7 @@
# png or ppm image files, one per previewed snippet. # png or ppm image files, one per previewed snippet.
# Pre-requisites: # Pre-requisites:
# * python 2.4 or later (subprocess module);
# * A latex executable; # * A latex executable;
# * preview.sty; # * preview.sty;
# * dvipng; # * dvipng;
@ -46,6 +47,10 @@
# --lilypond: Preprocess through lilypond-book. Default is false. # --lilypond: Preprocess through lilypond-book. Default is false.
# --lilypond-book=<exe>: # --lilypond-book=<exe>:
# The converter for lytex files. Default is lilypond-book. # The converter for lytex files. Default is lilypond-book.
#
# -d, --debug Show the output from external commands.
# -h, --help Show an help screen and exit.
# -v, --verbose Show progress messages.
# Decomposing TEXFILE's name as DIR/BASE.tex, this script will, # Decomposing TEXFILE's name as DIR/BASE.tex, this script will,
# if executed successfully, leave in DIR: # if executed successfully, leave in DIR:
@ -77,8 +82,8 @@ from legacy_lyxpreview2ppm import legacy_conversion, \
from lyxpreview_tools import copyfileobj, error, filter_pages, find_exe, \ from lyxpreview_tools import copyfileobj, error, filter_pages, find_exe, \
find_exe_or_terminate, join_metrics_and_rename, latex_commands, \ find_exe_or_terminate, join_metrics_and_rename, latex_commands, \
latex_file_re, make_texcolor, mkstemp, pdflatex_commands, run_command, \ latex_file_re, make_texcolor, mkstemp, pdflatex_commands, progress, \
warning, write_metrics_info run_command, warning, write_metrics_info
def usage(prog_name): def usage(prog_name):
@ -86,7 +91,6 @@ def usage(prog_name):
Usage: %s <options> <input file> Usage: %s <options> <input file>
Options: Options:
-h, --help: Show this help and exit
--dpi=<res>: Resolution per inch (default: 128) --dpi=<res>: Resolution per inch (default: 128)
--png, --ppm: Select the output format (default: png) --png, --ppm: Select the output format (default: png)
--fg=<color>: Foreground color (default: black, ie '000000') --fg=<color>: Foreground color (default: black, ie '000000')
@ -96,6 +100,10 @@ Options:
--lilypond-book=<exe>: --lilypond-book=<exe>:
The executable for lilypond-book (default: lilypond-book) The executable for lilypond-book (default: lilypond-book)
-d, --debug: Show the output from external commands
-h, --help: Show this help screen and exit
-v, --verbose: Show progress messages
The colors are hexadecimal strings, eg 'faf0e6'.""" The colors are hexadecimal strings, eg 'faf0e6'."""
return msg % prog_name return msg % prog_name
@ -200,7 +208,7 @@ def fix_latex_file(latex_file):
if changed: if changed:
copyfileobj(tmp, open(latex_file,"wb"), 1) copyfileobj(tmp, open(latex_file,"wb"), 1)
return return changed
def convert_to_ppm_format(pngtopnm, basename): def convert_to_ppm_format(pngtopnm, basename):
@ -211,7 +219,7 @@ def convert_to_ppm_format(pngtopnm, basename):
p2p_cmd = '%s "%s"' % (pngtopnm, png_file) p2p_cmd = '%s "%s"' % (pngtopnm, png_file)
p2p_status, p2p_stdout = run_command(p2p_cmd) p2p_status, p2p_stdout = run_command(p2p_cmd)
if p2p_status != None: if p2p_status:
error("Unable to convert %s to ppm format" % png_file) error("Unable to convert %s to ppm format" % png_file)
ppm = open(ppm_file, 'w') ppm = open(ppm_file, 'w')
@ -313,11 +321,11 @@ def main(argv):
# Parse and manipulate the command line arguments. # Parse and manipulate the command line arguments.
try: try:
(opts, args) = getopt.gnu_getopt(argv[1:], "h", ["bg=", (opts, args) = getopt.gnu_getopt(argv[1:], "dhv", ["bg=", "debug",
"dpi=", "fg=", "help", "latex=", "lilypond", "lilypond-book=", "dpi=", "fg=", "help", "latex=", "lilypond", "lilypond-book=",
"png", "ppm"]) "png", "ppm", "verbose"])
except getopt.GetoptError: except getopt.GetoptError, err:
error(usage(script_name)) error("%s\n%s" % (err, usage(script_name)))
opts.reverse() opts.reverse()
for opt, val in opts: for opt, val in opts:
@ -326,6 +334,9 @@ def main(argv):
sys.exit(0) sys.exit(0)
elif opt == "--bg": elif opt == "--bg":
bg_color = val bg_color = val
elif opt in ("-d", "--debug"):
import lyxpreview_tools
lyxpreview_tools.debug = True
elif opt == "--dpi": elif opt == "--dpi":
try: try:
dpi = string.atoi(val) dpi = string.atoi(val)
@ -341,12 +352,29 @@ def main(argv):
lilypond_book = [val] lilypond_book = [val]
elif opt in ("--png", "--ppm"): elif opt in ("--png", "--ppm"):
output_format = opt[2:] output_format = opt[2:]
elif opt in ("-v", "--verbose"):
import lyxpreview_tools
lyxpreview_tools.verbose = True
# Determine input file
if len(args) != 1: if len(args) != 1:
error(usage(script_name)) err = "A single input file is required, %s given" % (len(args) or "none")
error("%s\n%s" % (err, usage(script_name)))
input_path = args[0] input_path = args[0]
dir, latex_file = os.path.split(input_path) dir, latex_file = os.path.split(input_path)
# Echo the settings
progress("Starting %s..." % script_name)
progress("Output format: %s" % output_format)
progress("Foreground color: %s" % fg_color)
progress("Background color: %s" % bg_color)
progress("Resolution (dpi): %s" % dpi)
progress("File to process: %s" % input_path)
# Check for the input file
if not os.path.exists(input_path):
error('File "%s" not found.' % input_path)
if len(dir) != 0: if len(dir) != 0:
os.chdir(dir) os.chdir(dir)
@ -364,10 +392,18 @@ def main(argv):
# These flavors of latex are known to produce pdf output # These flavors of latex are known to produce pdf output
pdf_output = latex in pdflatex_commands pdf_output = latex in pdflatex_commands
progress("Latex command: %s" % latex)
progress("Latex produces pdf output: %s" % pdf_output)
progress("Lilypond-book command: %s" % lilypond_book)
progress("Preprocess through lilypond-book: %s" % lilypond)
progress("Altering the latex file for font size and colors")
# Omit font size specification in latex file. # Omit font size specification in latex file.
fix_latex_file(latex_file) if not fix_latex_file(latex_file):
warning("Unable to remove font size from the latex file")
if lilypond: if lilypond:
progress("Preprocess the latex file through %s" % lilypond_book)
if pdf_output: if pdf_output:
lilypond_book += ' --pdf' lilypond_book += ' --pdf'
@ -379,7 +415,7 @@ def main(argv):
lytex_call = '%s --safe --latex-program=%s "%s"' % (lilypond_book, lytex_call = '%s --safe --latex-program=%s "%s"' % (lilypond_book,
latex, lytex_file) latex, lytex_file)
lytex_status, lytex_stdout = run_command(lytex_call) lytex_status, lytex_stdout = run_command(lytex_call)
if lytex_status != None: if lytex_status:
warning("%s failed to compile %s" \ warning("%s failed to compile %s" \
% (os.path.basename(lilypond_book), lytex_file)) % (os.path.basename(lilypond_book), lytex_file))
@ -389,6 +425,7 @@ def main(argv):
# The data is input to legacy_conversion in as similar # The data is input to legacy_conversion in as similar
# as possible a manner to that input to the code used in # as possible a manner to that input to the code used in
# LyX 1.3.x. # LyX 1.3.x.
progress("Using the legacy conversion method (dvipng not found)")
vec = [ script_name, input_path, str(dpi), output_format, fg_color, bg_color, latex ] vec = [ script_name, input_path, str(dpi), output_format, fg_color, bg_color, latex ]
return legacy_conversion(vec) return legacy_conversion(vec)
@ -398,13 +435,13 @@ def main(argv):
# Move color information for PDF into the latex file. # Move color information for PDF into the latex file.
if not color_pdf(latex_file, bg_color_gr, fg_color_gr): if not color_pdf(latex_file, bg_color_gr, fg_color_gr):
error("Unable to move color info into the latex file") warning("Unable to move color info into the latex file")
# Compile the latex file. # Compile the latex file.
latex_call = '%s "%s"' % (latex, latex_file) latex_call = '%s "%s"' % (latex, latex_file)
latex_status, latex_stdout = run_command(latex_call) latex_status, latex_stdout = run_command(latex_call)
if latex_status != None: if latex_status:
warning("%s had problems compiling %s" \ warning("%s had problems compiling %s" \
% (os.path.basename(latex), latex_file)) % (os.path.basename(latex), latex_file))
@ -421,8 +458,8 @@ def main(argv):
# No DVI, is there a PDF? # No DVI, is there a PDF?
pdf_file = latex_file_re.sub(".pdf", latex_file) pdf_file = latex_file_re.sub(".pdf", latex_file)
if os.path.isfile(pdf_file): if os.path.isfile(pdf_file):
warning("%s produced a PDF output, fallback to legacy." % \ progress("%s produced a PDF output, fallback to legacy." \
(os.path.basename(latex))) % (os.path.basename(latex)))
return legacy_conversion_step2(latex_file, dpi, output_format) return legacy_conversion_step2(latex_file, dpi, output_format)
else: else:
error("No DVI or PDF output. %s failed." \ error("No DVI or PDF output. %s failed." \
@ -437,6 +474,7 @@ def main(argv):
# If all pages need PostScript, directly use the legacy method. # If all pages need PostScript, directly use the legacy method.
if len(ps_pages) == page_count: if len(ps_pages) == page_count:
vec = [ script_name, input_path, str(dpi), output_format, fg_color, bg_color, latex ] vec = [ script_name, input_path, str(dpi), output_format, fg_color, bg_color, latex ]
progress("Using the legacy conversion method (PostScript support)")
return legacy_conversion(vec) return legacy_conversion(vec)
# Run the dvi file through dvipng. # Run the dvi file through dvipng.
@ -444,10 +482,11 @@ def main(argv):
% (dvipng, dpi, fg_color_dvipng, bg_color_dvipng, pages_parameter, dvi_file) % (dvipng, dpi, fg_color_dvipng, bg_color_dvipng, pages_parameter, dvi_file)
dvipng_status, dvipng_stdout = run_command(dvipng_call) dvipng_status, dvipng_stdout = run_command(dvipng_call)
if dvipng_status != None: if dvipng_status:
warning("%s failed to generate images from %s... fallback to legacy method" \ warning("%s failed to generate images from %s... fallback to legacy method" \
% (os.path.basename(dvipng), dvi_file)) % (os.path.basename(dvipng), dvi_file))
# FIXME: skip unnecessary dvips trial in legacy_conversion_step2 # FIXME: skip unnecessary dvips trial in legacy_conversion_step2
progress("Using the legacy conversion method (dvipng failed)")
return legacy_conversion_step2(latex_file, dpi, output_format) return legacy_conversion_step2(latex_file, dpi, output_format)
# Extract metrics info from dvipng_stdout. # Extract metrics info from dvipng_stdout.
@ -464,6 +503,8 @@ def main(argv):
# Pass the new LaTeX file to the legacy method # Pass the new LaTeX file to the legacy method
vec = [ script_name, latex_file_re.sub("_legacy.tex", input_path), vec = [ script_name, latex_file_re.sub("_legacy.tex", input_path),
str(dpi), output_format, fg_color, bg_color, latex ] str(dpi), output_format, fg_color, bg_color, latex ]
progress("Pages %s include postscript specials" % ps_pages)
progress("Using the legacy conversion method (PostScript support)")
legacy_metrics = legacy_conversion(vec, True)[1] legacy_metrics = legacy_conversion(vec, True)[1]
# Now we need to mix metrics data from dvipng and the legacy method # Now we need to mix metrics data from dvipng and the legacy method

View File

@ -12,10 +12,16 @@
# A repository of the following functions, used by the lyxpreview2xyz scripts. # A repository of the following functions, used by the lyxpreview2xyz scripts.
# copyfileobj, error, find_exe, find_exe_or_terminate, make_texcolor, mkstemp, # copyfileobj, error, find_exe, find_exe_or_terminate, make_texcolor, mkstemp,
# run_command, warning # progress, run_command, warning
import os, re, string, sys, tempfile # Requires python 2.4 or later (subprocess module).
import os, re, string, subprocess, sys, tempfile
# Control the output to stdout
debug = False
verbose = False
# Known flavors of latex # Known flavors of latex
latex_commands = ("latex", "pplatex", "platex", "latex2e") latex_commands = ("latex", "pplatex", "platex", "latex2e")
@ -48,12 +54,18 @@ if os.name == "nt":
use_win32_modules = 0 use_win32_modules = 0
def progress(message):
global verbose
if verbose:
sys.stdout.write("Progress: %s\n" % message)
def warning(message): def warning(message):
sys.stderr.write(message + '\n') sys.stderr.write("Warning: %s\n" % message)
def error(message): def error(message):
sys.stderr.write(message + '\n') sys.stderr.write("Error: %s\n" % message)
sys.exit(1) sys.exit(1)
@ -99,10 +111,14 @@ def find_exe_or_terminate(candidates):
def run_command_popen(cmd): def run_command_popen(cmd):
handle = os.popen(cmd, 'r') pipe = subprocess.Popen(cmd, shell=True, close_fds=True, stdin=subprocess.PIPE, \
cmd_stdout = handle.read() stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
cmd_status = handle.close() cmd_stdout = pipe.communicate()[0]
cmd_status = pipe.returncode
global debug
if debug:
sys.stdout.write(cmd_stdout)
return cmd_status, cmd_stdout return cmd_status, cmd_stdout
@ -150,10 +166,14 @@ def run_command_win32(cmd):
if win32process.GetExitCodeProcess(process): if win32process.GetExitCodeProcess(process):
return -3, "" return -3, ""
return None, data global debug
if debug:
sys.stdout.write(data)
return 0, data
def run_command(cmd): def run_command(cmd):
progress("Running %s" % cmd)
if use_win32_modules: if use_win32_modules:
return run_command_win32(cmd) return run_command_win32(cmd)
else: else: