UI/Previewers/PreviewerHTML.py

changeset 3979
307b09aae43b
parent 3656
441956d8fce5
child 3981
5cd283505cfa
equal deleted inserted replaced
3978:59d99092a4de 3979:307b09aae43b
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
137 else: 145 else:
138 rootPath = "" 146 rootPath = ""
139 147
140 self.__processingThread.process( 148 self.__processingThread.process(
141 fn, language, editor.text(), 149 fn, language, editor.text(),
142 self.ssiCheckBox.isChecked(), rootPath) 150 self.ssiCheckBox.isChecked(), rootPath,
151 Preferences.getEditor("PreviewRestUseSphinx"))
143 152
144 def __setHtml(self, filePath, html): 153 def __setHtml(self, filePath, html):
145 """ 154 """
146 Private method to set the HTML to the view and restore the scroll bars 155 Private method to set the HTML to the view and restore the scroll bars
147 positions. 156 positions.
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")
382 480
383 @param text text to be processed (string) 481 @param text text to be processed (string)
384 @return processed HTML (string) 482 @return processed HTML (string)
385 """ 483 """
386 try: 484 try:
387 import markdown # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ 485 import markdown # __IGNORE_EXCEPTION__
388 except ImportError: 486 except ImportError:
389 return self.tr( 487 return self.tr(
390 """<p>Markdown preview requires the <b>python-markdown</b> """ 488 """<p>Markdown preview requires the <b>python-markdown</b> """
391 """package.<br/>Install it with your package manager or see """ 489 """package.<br/>Install it with your package manager or see """
392 """<a href="http://pythonhosted.org/Markdown/install.html">""" 490 """<a href="http://pythonhosted.org/Markdown/install.html">"""

eric ide

mercurial