summaryrefslogtreecommitdiff
path: root/support/autolatex/plugins/sublime-text-2/utils/runner.py
blob: d5725e00cba55398b75f8db4c57b07a20616bcfa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# autolatex/utils/runner.py
# Copyright (C) 2013  Stephane Galland <galland@arakhne.org>
#
# 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)