7 Module implementing a previewer widget for HTML, Markdown and ReST files. |
7 Module implementing a previewer widget for HTML, Markdown and ReST files. |
8 """ |
8 """ |
9 |
9 |
10 from __future__ import unicode_literals |
10 from __future__ import unicode_literals |
11 |
11 |
|
12 try: # Only for Py2 |
|
13 import StringIO as io # __IGNORE_EXCEPTION__ |
|
14 except ImportError: |
|
15 import io # __IGNORE_WARNING__ |
|
16 |
12 import os |
17 import os |
13 import threading |
18 import threading |
14 import re |
19 import re |
|
20 import shutil |
|
21 import tempfile |
|
22 import sys |
15 |
23 |
16 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QSize, QThread |
24 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QSize, QThread |
17 from PyQt5.QtWidgets import QWidget |
25 from PyQt5.QtWidgets import QWidget |
18 from PyQt5.QtWebKitWidgets import QWebPage |
26 from PyQt5.QtWebKitWidgets import QWebPage |
19 |
27 |
227 @signal htmlReady(str,str) emitted with the file name and processed HTML |
236 @signal htmlReady(str,str) emitted with the file name and processed HTML |
228 to signal the availability of the processed HTML |
237 to signal the availability of the processed HTML |
229 """ |
238 """ |
230 htmlReady = pyqtSignal(str, str) |
239 htmlReady = pyqtSignal(str, str) |
231 |
240 |
|
241 DefaultStaticPath = os.path.join( |
|
242 os.path.abspath(os.path.dirname(__file__)), 'sphinx_default') |
|
243 StaticRegexp = re.compile(r'(src|href)=["\']_static([\s\w/\.]+?)["\']', |
|
244 re.IGNORECASE) |
|
245 |
232 def __init__(self, parent=None): |
246 def __init__(self, parent=None): |
233 """ |
247 """ |
234 Constructor |
248 Constructor |
235 |
249 |
236 @param parent reference to the parent object (QObject) |
250 @param parent reference to the parent object (QObject) |
237 """ |
251 """ |
238 super(PreviewProcessingThread, self).__init__() |
252 super(PreviewProcessingThread, self).__init__() |
239 |
253 |
240 self.__lock = threading.Lock() |
254 self.__lock = threading.Lock() |
241 |
255 |
242 def process(self, filePath, language, text, ssiEnabled, rootPath): |
256 def process(self, filePath, language, text, ssiEnabled, rootPath, |
|
257 useSphinx): |
243 """ |
258 """ |
244 Public method to convert the given text to HTML. |
259 Public method to convert the given text to HTML. |
245 |
260 |
246 @param filePath file path of the text (string) |
261 @param filePath file path of the text (string) |
247 @param language language of the text (string) |
262 @param language language of the text (string) |
248 @param text text to be processed (string) |
263 @param text text to be processed (string) |
249 @param ssiEnabled flag indicating to do some (limited) SSI processing |
264 @param ssiEnabled flag indicating to do some (limited) SSI processing |
250 (boolean) |
265 (boolean) |
251 @param rootPath root path to be used for SSI processing (str) |
266 @param rootPath root path to be used for SSI processing (str) |
|
267 @param useSphinx flag indicating to use Sphinx to generate the |
|
268 ReST preview (boolean) |
252 """ |
269 """ |
253 with self.__lock: |
270 with self.__lock: |
254 self.__filePath = filePath |
271 self.__filePath = filePath |
255 self.__language = language |
272 self.__language = language |
256 self.__text = text |
273 self.__text = text |
257 self.__ssiEnabled = ssiEnabled |
274 self.__ssiEnabled = ssiEnabled |
258 self.__rootPath = rootPath |
275 self.__rootPath = rootPath |
259 self.__haveData = True |
276 self.__haveData = True |
|
277 self.__useSphinx = useSphinx |
260 if not self.isRunning(): |
278 if not self.isRunning(): |
261 self.start(QThread.LowPriority) |
279 self.start(QThread.LowPriority) |
262 |
280 |
263 def run(self): |
281 def run(self): |
264 """ |
282 """ |
270 filePath = self.__filePath |
288 filePath = self.__filePath |
271 language = self.__language |
289 language = self.__language |
272 text = self.__text |
290 text = self.__text |
273 ssiEnabled = self.__ssiEnabled |
291 ssiEnabled = self.__ssiEnabled |
274 rootPath = self.__rootPath |
292 rootPath = self.__rootPath |
|
293 useSphinx = self.__useSphinx |
275 self.__haveData = False |
294 self.__haveData = False |
276 |
295 |
277 html = self.__getHtml(language, text, ssiEnabled, filePath, |
296 html = self.__getHtml(language, text, ssiEnabled, filePath, |
278 rootPath) |
297 rootPath, useSphinx) |
279 |
298 |
280 with self.__lock: |
299 with self.__lock: |
281 if not self.__haveData: |
300 if not self.__haveData: |
282 self.htmlReady.emit(filePath, html) |
301 self.htmlReady.emit(filePath, html) |
283 break |
302 break |
284 # else - next iteration |
303 # else - next iteration |
285 |
304 |
286 def __getHtml(self, language, text, ssiEnabled, filePath, rootPath): |
305 def __getHtml(self, language, text, ssiEnabled, filePath, rootPath, |
|
306 useSphinx): |
287 """ |
307 """ |
288 Private method to process the given text depending upon the given |
308 Private method to process the given text depending upon the given |
289 language. |
309 language. |
290 |
310 |
291 @param language language of the text (string) |
311 @param language language of the text (string) |
292 @param text to be processed (string) |
312 @param text to be processed (string) |
293 @param ssiEnabled flag indicating to do some (limited) SSI processing |
313 @param ssiEnabled flag indicating to do some (limited) SSI processing |
294 (boolean) |
314 (boolean) |
295 @param filePath file path of the text (string) |
315 @param filePath file path of the text (string) |
296 @param rootPath root path to be used for SSI processing (str) |
316 @param rootPath root path to be used for SSI processing (str) |
|
317 @param useSphinx flag indicating to use Sphinx to generate the |
|
318 ReST preview (boolean) |
297 @return processed HTML text (string) |
319 @return processed HTML text (string) |
298 """ |
320 """ |
299 if language == "HTML": |
321 if language == "HTML": |
300 if ssiEnabled: |
322 if ssiEnabled: |
301 return self.__processSSI(text, filePath, rootPath) |
323 return self.__processSSI(text, filePath, rootPath) |
302 else: |
324 else: |
303 return text |
325 return text |
304 elif language == "Markdown": |
326 elif language == "Markdown": |
305 return self.__convertMarkdown(text) |
327 return self.__convertMarkdown(text) |
306 elif language == "ReST": |
328 elif language == "ReST": |
307 return self.__convertReST(text) |
329 return self.__convertReST(text, useSphinx) |
308 else: |
330 else: |
309 return self.tr( |
331 return self.tr( |
310 "<p>No preview available for this type of file.</p>") |
332 "<p>No preview available for this type of file.</p>") |
311 |
333 |
312 def __processSSI(self, txt, filename, root): |
334 def __processSSI(self, txt, filename, root): |
354 incTxt = "" |
376 incTxt = "" |
355 txt = txt[:incMatch.start(0)] + incTxt + txt[incMatch.end(0):] |
377 txt = txt[:incMatch.start(0)] + incTxt + txt[incMatch.end(0):] |
356 |
378 |
357 return txt |
379 return txt |
358 |
380 |
359 def __convertReST(self, text): |
381 def __convertReST(self, text, useSphinx): |
360 """ |
382 """ |
361 Private method to convert ReST text into HTML. |
383 Private method to convert ReST text into HTML. |
|
384 |
|
385 @param text text to be processed (string) |
|
386 @param useSphinx flag indicating to use Sphinx to generate the |
|
387 ReST preview (boolean) |
|
388 @return processed HTML (string) |
|
389 """ |
|
390 if useSphinx: |
|
391 return self.__convertReSTSphinx(text) |
|
392 else: |
|
393 return self.__convertReSTDocutils(text) |
|
394 |
|
395 def __convertReSTSphinx(self, text): |
|
396 """ |
|
397 Private method to convert ReST text into HTML using 'sphinx'. |
362 |
398 |
363 @param text text to be processed (string) |
399 @param text text to be processed (string) |
364 @return processed HTML (string) |
400 @return processed HTML (string) |
365 """ |
401 """ |
366 try: |
402 try: |
367 import docutils.core # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
403 from sphinx.application import Sphinx # __IGNORE_EXCEPTION__ |
|
404 except ImportError: |
|
405 return self.tr( |
|
406 """<p>ReStructuredText preview requires the""" |
|
407 """ <b>sphinx</b> package.<br/>Install it with""" |
|
408 """ your package manager,'pip install Sphinx' or see""" |
|
409 """ <a href="http://pypi.python.org/pypi/Sphinx">""" |
|
410 """this page.</a></p>""" |
|
411 """<p>Alternatively you may disable Sphinx usage""" |
|
412 """ on the Editor, Filehandling configuration page.</p>""") |
|
413 |
|
414 tempDir = tempfile.mkdtemp(prefix='eric-rest-') |
|
415 try: |
|
416 filename = 'sphinx_preview' |
|
417 basePath = os.path.join(tempDir, filename) |
|
418 fh = open(basePath + '.rst', 'w', encoding='utf-8') |
|
419 fh.write(text) |
|
420 fh.close() |
|
421 |
|
422 overrides = {'html_add_permalinks': False, |
|
423 'html_copy_source': False, |
|
424 'html_title': 'Sphinx preview', |
|
425 'html_use_index': False, |
|
426 'html_use_modindex': False, |
|
427 'html_use_smartypants': True, |
|
428 'master_doc': filename } |
|
429 app = Sphinx(srcdir=tempDir, confdir=None, outdir=tempDir, |
|
430 doctreedir=tempDir, buildername='html', |
|
431 confoverrides=overrides, status=None, |
|
432 warning=io.StringIO()) |
|
433 app.build(force_all=True, filenames=None) |
|
434 |
|
435 fh = open(basePath + '.html', 'r', encoding='utf-8') |
|
436 html = fh.read() |
|
437 fh.close() |
|
438 finally: |
|
439 shutil.rmtree(tempDir) |
|
440 |
|
441 # Replace the "_static/..." references inserted by Sphinx with absolute |
|
442 # links to the specified DefaultStaticPath replacement. |
|
443 def replace(m): |
|
444 return '{0}="file://{1}{2}"'.format( |
|
445 m.group(1), self.DefaultStaticPath, m.group(2)) |
|
446 html = re.sub(self.StaticRegexp, replace, html) |
|
447 |
|
448 return html |
|
449 |
|
450 def __convertReSTDocutils(self, text): |
|
451 """ |
|
452 Private method to convert ReST text into HTML using 'docutils'. |
|
453 |
|
454 @param text text to be processed (string) |
|
455 @return processed HTML (string) |
|
456 """ |
|
457 if 'sphinx' in sys.modules: |
|
458 # Make sure any Sphinx polution of docutils has been removed. |
|
459 unloadKeys = [k for k in sys.modules.keys() |
|
460 if k.startswith(('docutils', 'sphinx'))] |
|
461 for key in unloadKeys: |
|
462 sys.modules.pop(key) |
|
463 |
|
464 try: |
|
465 import docutils.core # __IGNORE_EXCEPTION__ |
368 except ImportError: |
466 except ImportError: |
369 return self.tr( |
467 return self.tr( |
370 """<p>ReStructuredText preview requires the""" |
468 """<p>ReStructuredText preview requires the""" |
371 """ <b>python-docutils</b> package.<br/>Install it with""" |
469 """ <b>python-docutils</b> package.<br/>Install it with""" |
372 """ your package manager or see""" |
470 """ your package manager, 'pip install docutils' or see""" |
373 """ <a href="http://pypi.python.org/pypi/docutils">""" |
471 """ <a href="http://pypi.python.org/pypi/docutils">""" |
374 """this page.</a></p>""") |
472 """this page.</a></p>""") |
375 |
473 |
376 return docutils.core.publish_string(text, writer_name='html')\ |
474 return docutils.core.publish_string(text, writer_name='html')\ |
377 .decode("utf-8") |
475 .decode("utf-8") |