#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# autolatex/utils/runner.py
# Copyright (C) 2013 Stephane Galland <
[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import os
import re
import subprocess
# Try to use the threading library if it is available
try:
import threading as _threading
except ImportError:
import dummy_threading as _threading
import utils
# List of all the runners
_all_runners = []
def kill_all_runners():
global _all_runners
tab = _all_runners
_all_runners = []
for r in tab:
r.cancel()
# Launch AutoLaTeX inside a thread, and wait for the result
class Listener(object):
def get_runner_progress(self):
return False
def on_runner_add_ui(self):
pass
def on_runner_remove_ui(self):
pass
def on_runner_progress(self, amount, comment):
pass
def on_runner_finalize_execution(self, retcode, output, latex_warnings):
pass
# Launch AutoLaTeX inside a thread, and wait for the result
class Runner(_threading.Thread):
# listener is the listener on the events
# directory is the path to set as the current path
# directive is the AutoLaTeX command
# params are the CLI options for AutoLaTeX
def __init__(self, listener, directory, directive, params):
_threading.Thread.__init__(self)
assert listener
self.daemon = True
self._listener = listener
self._directory = directory
self._cmd = [ utils.AUTOLATEX_BINARY, '--file-line-warning' ] + params
if directive:
self._cmd.append(directive)
self._has_progress = False
self._subprocess = None
# Cancel the execution
def cancel(self):
if self._subprocess:
self._subprocess.terminate()
self._subprocess = None
if self._has_progress:
# Remove the info bar from the inside of the UI thread
self._listener.on_runner_remove_ui()
# Update the rest of the UI from the inside of the UI thread
self._listener.on_runner_finalize_execution(0, '', [])
# Run the thread
def run(self):
global _all_runners
_all_runners.append(self)
progress_line_pattern = None
self._has_progress = self._listener.get_runner_progress()
if self._has_progress:
# Add the progress UI
self._listener.on_runner_add_ui()
# Update the command line to obtain the progress data
self._cmd.append('--progress=n')
# Compile a regular expression to extract the progress amount
progress_line_pattern = re.compile("^\\[\\s*([0-9]+)\\%\\]\\s+[#.]+(.*)$")
# Launch the subprocess
os.chdir(self._directory)
self._subprocess = subprocess.Popen(self._cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = ''
if self._subprocess:
if self._has_progress:
# Use the info bar to draw the progress of the task
if self._subprocess:
self._subprocess.poll()
# Loop until the subprocess is dead
while self._subprocess and self._subprocess.returncode is None:
if self._subprocess and not self._subprocess.stdout.closed:
# Read a line from STDOUT and extract the progress amount
if self._subprocess:
self._subprocess.stdout.flush()
if self._subprocess:
line = self._subprocess.stdout.readline()
if line:
mo = re.match(progress_line_pattern, line)
if mo:
amount = (float(mo.group(1)) / 100.)
comment = mo.group(2).strip()
self._listener.on_runner_progress(amount, comment)
if self._subprocess:
self._subprocess.poll()
# Kill the subprocess if
proc = self._subprocess
if proc:
retcode = proc.returncode
# Read the error output of AutoLaTeX
proc.stderr.flush()
for line in proc.stderr:
output = output + line
proc.stdout.close()
proc.stderr.close()
else:
# Silent execution of the task
out, err = self._subprocess.communicate() if self._subprocess else ('','')
retcode = self._subprocess.returncode if self._subprocess else 0
output = err
# Stop because the subprocess was cancelled
if not self._subprocess:
if self in _all_runners:
_all_runners.remove(self)
return 0
self._subprocess = None
# If AutoLaTeX had failed, the output is assumed to
# be the error message.
# If AutoLaTeX had not failed, the output may contains
# "warning" notifications.
latex_warnings = []
if retcode == 0:
regex_expr = re.compile("^\\!\\!(.+?):(W[0-9]+):[^:]+:\\s*(.+?)\\s*$")
for output_line in re.split("[\n\r]+", output):
mo = re.match(regex_expr, output_line)
if mo:
latex_warnings.append([mo.group(3),mo.group(1), mo.group(2)])
output = '' # Output is no more interesting
if self._has_progress:
# Remove the info bar from the inside of the UI thread
self._listener.on_runner_remove_ui()
# Update the rest of the UI from the inside of the UI thread
self._listener.on_runner_finalize_execution(retcode, output, latex_warnings)
if self in _all_runners:
_all_runners.remove(self)