Пример #1
0
 def __init__(self):
     QThread.__init__(self)
     self._queue = queue.Queue()
     self.start(QThread.LowPriority)
     self._ac = AsyncController('QThread', self)
     self._ac.defaultPriority = QThread.LowPriority
     self._SphinxInvocationCount = 1
Пример #2
0
    def test_9(self):
        for _ in self.poolAndThread:
            with AsyncController(_) as ac:

                def f():
                    raise TypeError

                em = Emitter()
                # Fun with exceptions: if an exception is raised while handling
                # a signal, it doesn't show up with normal try/catch semantics.
                # Instead, ``sys.excepthook`` must be overridden to see it.
                # ``WaitForSignal`` does this in ``__exit__``, so use it for the
                # convenience. Put another way, removing ``WaitForSignal`` and
                # adding a time.sleep(1.0) produces test failures, since the
                # exceptions raised are not caught by standard Python mechanisms
                # (here, ``self.assertRaises``).
                #
                # **However**, ``WaitForSignal`` doesn't do this in the body of
                # the ``with`` statement, so 'Sync' raises an exception but this
                # is discarded. For simplicity, skip this test case for now.
                with self.assertRaises(TypeError), WaitForSignal(
                        em.bing, 1000, printExcTraceback=False):
                    ac.start(em.g, f)

                # Make sure that the exception is still raised even if g doesn't
                # check for it.
                with self.assertRaises(TypeError), WaitForSignal(
                        em.bing, 1000, printExcTraceback=False):
                    ac.start(lambda result: None, f)

                # Make sure that the exception is still raised even there is no
                # g to check for it.
                with self.assertRaises(TypeError), WaitForSignal(
                        em.bing, 1000, printExcTraceback=False):
                    ac.start(None, f)
Пример #3
0
 def test_5(self):
     for _ in self.poolAndThread:
         with AsyncController(_) as ac:
             def f(currentThread):
                 self.assertNotEqual(currentThread, QThread.currentThread())
             em = Emitter()
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, f, QThread.currentThread())
Пример #4
0
 def test_10(self):
     with AsyncController('QThread') as ac:
         em1 = Emitter()
         def f1():
             ac.start(em1.g, lambda: QThread.currentThread())
         with WaitForSignal(em1.bing, 1000):
             ac.start(None, f1)
         self.assertEquals(em1.thread, em1.result)
Пример #5
0
 def test_17(self):
     for _ in self.singleThreadOnly:
         with AsyncController(_) as ac:
             f = ac._wrap(self.fail, lambda: None)
             f.cancel()
             ac._start(f)
             em = Emitter()
             # Make sure the canceled job was processed by waiting until the
             # next job finishes.
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, lambda: None)
Пример #6
0
 def test_4(self):
     for _ in self.syncPoolAndThread:
         with AsyncController(_) as ac:
             def f(a, b, c=2, d=4):
                 self.assertEqual(a, 2)
                 self.assertEqual(b, 3)
                 self.assertEqual(c, 4)
                 self.assertEqual(d, 5)
             em = Emitter()
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, f, 2, 3, d=5, c=4)
Пример #7
0
 def test_7(self):
     for _ in ('Sync', 'QThread'):
         with AsyncController(_) as ac:
             em1 = Emitter(15, self.assertEqual)
             em2 = Emitter(16, self.assertEqual)
             em3 = Emitter(17, self.assertEqual)
             ac.start(em1.g, lambda: 15)
             ac.start(em2.g, lambda: 16)
             future3 = ac._wrap(em3.g, lambda: 17)
             with WaitForSignal(em3.bing, 1000):
                 ac._start(future3)
Пример #8
0
 def test_1(self):
     for _ in self.syncPoolAndThread:
         with AsyncController(_) as ac:
             # gotHere must be a list in order to f to change it in a way
             # that is visible outside of f.
             gotHere = [False]
             def f():
                 gotHere[0] = True
             future = ac._wrap(None, f)
             with WaitForSignal(future._signalInvoker.doneSignal, 1000):
                 ac._start(future)
             self.assertTrue(gotHere[0])
Пример #9
0
    def test_11(self):
        # Don't test with one pooled thread -- this test expects at least two
        # threads.
        with AsyncController(2) as ac:

            def f1():
                em2 = Emitter()
                with WaitForSignal(em2.bing, 1000):
                    ac.start(em2.g, lambda x: x, QThread.currentThread())
                self.assertEqual(em2.thread, em2.result)

            em1 = Emitter()
            with WaitForSignal(em1.bing, 1000):
                ac.start(em1.g, f1)
Пример #10
0
 def test_14(self):
     for _ in self.poolAndThread:
         with AsyncController(_) as ac:
             def f(assertEqual, priority):
                 assertEqual(QThread.currentThread().priority(), priority)
             em = Emitter()
             ac.defaultPriority = QThread.LowPriority
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, f, self.assertEqual, QThread.LowestPriority,
                          _futurePriority=QThread.LowestPriority)
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, f, self.assertEqual, QThread.LowPriority)
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, f, self.assertEqual, QThread.HighestPriority,
                          _futurePriority=QThread.HighestPriority)
Пример #11
0
 def test_11(self):
     # Don't test with one pooled thread -- this test expects at least two
     # threads.
     with AsyncController(2) as ac:
         em2 = Emitter()
         def f2():
             future = ac.start(em2.g, lambda x: x, QThread.currentThread())
             # The doneSignal won't be processed without an event loop. A
             # thread pool doesn't create one, so make our own to run ``g``.
             qe = QEventLoop()
             future._signalInvoker.doneSignal.connect(qe.exit)
             qe.exec_()
         with WaitForSignal(em2.bing, 1000):
             ac.start(None, f2)
         self.assertEquals(em2.thread, em2.result)
Пример #12
0
 def test_6(self):
     # Don't test with one pooled thread -- this test expects at least two
     # threads.
     with AsyncController(2) as ac:
         q = Queue()
         def f():
             q.get()
             return QThread.currentThread()
         em1 = Emitter()
         em2 = Emitter()
         ac.start(em1.g, f)
         ac.start(em2.g, f)
         with WaitForSignal(em1.bing, 1000), WaitForSignal(em2.bing, 1000):
             q.put(None)
             q.put(None)
         s = set([em1.result, em2.result, QThread.currentThread()])
         self.assertEquals(len(s), 3)
Пример #13
0
    def test_18(self):
        for _ in self.syncPoolAndThread:
            # Terminate using a context manager.
            with AsyncController(_) as ac1:
                pass
            with self.assertRaises(AssertionError):
                ac1.start(None, lambda: None)

            # Termiante by calling terminate.
            ac2 = AsyncController(_)
            ac2.terminate()
            with self.assertRaises(AssertionError):
                ac2.start(None, lambda: None)

            # Terminate via the QT object tree.
            o = QObject()
            ac3 = AsyncController(_, o)
            sip.delete(o)
            with self.assertRaises(AssertionError):
                ac3.start(None, lambda: None)
Пример #14
0
 def test_8(self):
     for _ in self.poolOnly:
         with AsyncController(_) as ac:
             q1 = Queue()
             q2 = Queue()
             q3 = Queue()
             em1 = Emitter(15, self.assertEqual)
             em2 = Emitter(16, self.assertEqual)
             em3 = Emitter(17, self.assertEqual)
             ac.start(em1.g, lambda: q1.get())
             ac.start(em2.g, lambda: q2.get())
             ac.start(em3.g, lambda: q3.get())
             sc = SignalCombiner()
             em1.bing.connect(sc.onBing)
             em2.bing.connect(sc.onBing)
             em3.bing.connect(sc.onBing)
             with WaitForSignal(sc.allEmitted, 1000):
                 q1.put(15)
                 q2.put(16)
                 q3.put(17)
Пример #15
0
    def test_12(self):
        for _ in self.singleThreadOnly:
            with AsyncController(_) as ac:
                q1a = Queue()
                q1b = Queue()
                def f1():
                    q1b.put(None)
                    q1a.get()
                em1 = Emitter()
                future1 = ac.start(em1.g, f1)
                q1b.get()
                self.assertEquals(future1.state, Future.STATE_RUNNING)

                future2 = ac.start(None, lambda: None)
                QTest.qWait(100)
                self.assertEquals(future2.state, Future.STATE_WAITING)
                with WaitForSignal(em1.bing, 1000):
                    future2.cancel()
                    q1a.put(None)
                self.assertEquals(future1.state, Future.STATE_FINISHED)
                QTest.qWait(100)
                self.assertEquals(future2.state, Future.STATE_CANCELED)
Пример #16
0
    def test_13(self):
        for _ in self.singleThreadOnly:
            with AsyncController(_) as ac:
                q1a = Queue()
                q1b = Queue()

                def f1():
                    q1b.put(None)
                    q1a.get()

                # Cancel future3 while it's running in the other thread.
                em1 = Emitter('em1 should never be called by {}'.format(_),
                              self.assertEqual)
                em1.bing.connect(self.fail)
                future1 = ac.start(em1.g, f1)
                q1b.get()
                self.assertEqual(future1.state, Future.STATE_RUNNING)
                future1.cancel(True)
                q1a.put(None)
                # If the result is discarded, it should never emit a signal or
                # invoke its callback, even if the task is already running. Wait
                # to make sure neither happened.
                QTest.qWait(100)

                # In addition, the signal from a finished task that is discarded
                # should not invoke the callback, even after the task has
                # finihsed and the sigal emitted.
                em2 = Emitter('em2 should never be called be {}'.format(_),
                              self.assertEqual)
                em2.bing.connect(self.fail)
                future2 = ac.start(em2.g, lambda: None)
                # Don't use qWait here, since it will process messages, which
                # causes em2.g to be invoked.
                time.sleep(0.1)
                self.assertEqual(future2.state, Future.STATE_FINISHED)
                future2.cancel(True)  # Test per-task priority.
                # Wait, in case a pending signal will invoke em2.g.
                QTest.qWait(100)
Пример #17
0
class ConverterThread(QThread):
    """Thread converts markdown to HTML.
    """

    # This signal is emitted by the converter thread when a file has been
    # converted to HTML.
    htmlReady = pyqtSignal(
      # Path to the file which should be converted to / displayed as HTML.
      str,
      # HTML rendering of the file; empty if the HTML is provided in a file
      # specified by the URL below.
      str,
      # Error text resulting from the conversion process.
      str,
      # A reference to a file containing HTML rendering. Empty if the second
      # parameter above contains the HTML instead.
      QUrl)

    # This signal clears the context of the log window.
    logWindowClear = pyqtSignal()

    # This signal emits messages for the log window.
    logWindowText = pyqtSignal(
      # A string to append to the log window.
      str)

    _Task = collections.namedtuple("Task", ["filePath", "language", "text"])

    def __init__(self):
        QThread.__init__(self)
        self._queue = queue.Queue()
        self.start(QThread.LowPriority)
        self._ac = AsyncController('QThread', self)
        self._ac.defaultPriority = QThread.LowPriority
        self._SphinxInvocationCount = 1

    def process(self, filePath, language, text):
        """Convert data and emit result.
        """
        self._queue.put(self._Task(filePath, language, text))

    def stop_async(self):
        self._queue.put(None)

    def _getHtml(self, language, text, filePath):
        """Get HTML for document
        """
        if language == 'Markdown':
            return self._convertMarkdown(text), None, QUrl()
        # For ReST, use docutils only if Sphinx isn't available.
        elif language == 'Restructured Text' and not sphinxEnabledForFile(filePath):
            htmlUnicode, errString = self._convertReST(text)
            return htmlUnicode, errString, QUrl()
        elif filePath and sphinxEnabledForFile(filePath):  # Use Sphinx to generate the HTML if possible.
            return self._convertSphinx(filePath)
        elif filePath and canUseCodeChat(filePath):  # Otherwise, fall back to using CodeChat+docutils.
            return self._convertCodeChat(text, filePath)
        else:
            return 'No preview for this type of file', None, QUrl()

    def _convertMarkdown(self, text):
        """Convert Markdown to HTML
        """
        try:
            import markdown
        except ImportError:
            return 'Markdown preview requires <i>python-markdown</i> package<br/>' \
                   'Install it with your package manager or see ' \
                   '<a href="http://packages.python.org/Markdown/install.html">installation instructions</a>'

        extensions = ['fenced_code', 'nl2br', 'tables', 'enki.plugins.preview.mdx_math']

        # version 2.0 supports only extension names, not instances
        if markdown.version_info[0] > 2 or \
           (markdown.version_info[0] == 2 and markdown.version_info[1] > 0):

            class _StrikeThroughExtension(markdown.Extension):
                """http://achinghead.com/python-markdown-adding-insert-delete.html
                Class is placed here, because depends on imported markdown, and markdown import is lazy
                """
                DEL_RE = r'(~~)(.*?)~~'

                def extendMarkdown(self, md, md_globals):
                    # Create the del pattern
                    delTag = markdown.inlinepatterns.SimpleTagPattern(self.DEL_RE, 'del')
                    # Insert del pattern into markdown parser
                    md.inlinePatterns.add('del', delTag, '>not_strong')

            extensions.append(_StrikeThroughExtension())

        return markdown.markdown(text, extensions)

    def _convertReST(self, text):
        """Convert ReST
        """
        try:
            import docutils.core
            import docutils.writers.html4css1
        except ImportError:
            return 'Restructured Text preview requires the <i>python-docutils</i> package.<br/>' \
                   'Install it with your package manager or see ' \
                   '<a href="http://pypi.python.org/pypi/docutils"/>this page.</a>', None

        errStream = io.StringIO()
        settingsDict = {
          # Make sure to use Unicode everywhere.
          'output_encoding': 'unicode',
          'input_encoding' : 'unicode',
          # Don't stop processing, no matter what.
          'halt_level'     : 5,
          # Capture errors to a string and return it.
          'warning_stream' : errStream }
        # Frozen-specific settings.
        if isFrozen:
            settingsDict['template'] = (
              # The default docutils stylesheet and template uses a relative path,
              # which doesn't work when frozen ???. Under Unix when not frozen,
              # it produces:
              # ``IOError: [Errno 2] No such file or directory:
              # '/usr/lib/python2.7/dist-packages/docutils/writers/html4css1/template.txt'``.
              os.path.join(os.path.dirname(docutils.writers.html4css1.__file__),
                           docutils.writers.html4css1.Writer.default_template) )
            settingsDict['stylesheet_dirs'] = ['.',
                                               os.path.dirname(docutils.writers.html4css1.__file__)]
        htmlString = docutils.core.publish_string(text, writer_name='html',
                                                  settings_overrides=settingsDict)
        errString = errStream.getvalue()
        errStream.close()
        return htmlString, errString

    def _convertSphinx(self, filePath):
        # Run the builder.
        errString = self._runHtmlBuilder()

        # Look for the HTML output.
        #
        # Get an absolute path to the output path, which could be relative.
        outputPath = core.config()['Sphinx']['OutputPath']
        projectPath = core.config()['Sphinx']['ProjectPath']
        if not os.path.isabs(outputPath):
            outputPath = os.path.join(projectPath, outputPath)
        # Create an htmlPath as OutputPath + remainder of filePath.
        htmlPath = os.path.join(outputPath + filePath[len(projectPath):])
        html_file_suffix = '.html'
        try:
            with codecs.open(os.path.join(projectPath, 'sphinx-enki-info.txt')) as f:
                hfs = f.read()
                # If the file is empty, then html_file_suffix wasn't defined
                # or is None. In this case, use the default extension.
                # Otherwise, use the extension read from the file.
                if hfs:
                    html_file_suffix = hfs
        except:
            errString = "Warning: assuming .html extension. Use " + \
                "the conf.py template to set the extension.\n" + errString
            pass
        # First place to look: file.html. For example, look for foo.py
        # in foo.py.html.
        htmlFile = htmlPath + html_file_suffix
        # Second place to look: file without extension.html. For
        # example, look for foo.html for foo.rst.
        htmlFileAlter = os.path.splitext(htmlPath)[0] + html_file_suffix
        # Check that the output file produced by Sphinx is newer than
        # the source file it was built from.
        if os.path.exists(htmlFile):
            return _checkModificationTime(filePath, htmlFile, errString)
        elif os.path.exists(htmlFileAlter):
            return _checkModificationTime(filePath, htmlFileAlter, errString)
        else:
            return ('No preview for this type of file.<br>Expected ' +
                    htmlFile + " or " + htmlFileAlter, errString, QUrl())

    def _convertCodeChat(self, text, filePath):
        # Use StringIO to pass CodeChat compilation information back to
        # the UI.
        errStream = io.StringIO()
        try:
            htmlString = CodeToRest.code_to_html_string(text, errStream,
                                                        filename=filePath)
        except KeyError:
            # Although the file extension may be in the list of supported
            # extensions, CodeChat may not support the lexer chosen by Pygments.
            # For example, a ``.v`` file may be Verilog (supported by CodeChat)
            # or Coq (not supported). In this case, provide an error messsage
            errStream.write('Error: this file is not supported by CodeChat.')
            htmlString = ''
        errString = errStream.getvalue()
        errStream.close()
        return htmlString, errString, QUrl()

    def _runHtmlBuilder(self):
        # Build the commond line for Sphinx.
        if core.config()['Sphinx']['AdvancedMode']:
            htmlBuilderCommandLine = core.config()['Sphinx']['Cmdline']
            if sys.platform.startswith('linux'):
                # If Linux is used, then subprocess cannot take the whole
                # commandline as the name of an executable file. Module shlex
                # has to be used to parse commandline.
                htmlBuilderCommandLine = shlex.split(htmlBuilderCommandLine)
        else:
            # For available builder options, refer to: http://sphinx-doc.org/builders.html
            htmlBuilderCommandLine = [core.config()['Sphinx']['Executable'],
              # Place doctrees in the ``_build`` directory; by default, Sphinx
              # places this in _build/html/.doctrees.
              '-d', os.path.join('_build', 'doctrees'),
              # Source directory -- the current directory, since we'll chdir to
              # the project directory before executing this.
              '.',
              # Build directory
              core.config()['Sphinx']['OutputPath']]

        # Invoke it.
        try:
            # Clear the log at the beginning of a Sphinx build.
            self.logWindowClear.emit()

            cwd = core.config()['Sphinx']['ProjectPath']
            # If the command line is already a string (advanced mode), just print it.
            # Otherwise, it's a list that should be transformed to a string.
            if isinstance(htmlBuilderCommandLine, str):
                htmlBuilderCommandLineStr = htmlBuilderCommandLine
            else:
                htmlBuilderCommandLineStr = ' '.join(htmlBuilderCommandLine)
            self.logWindowText.emit('{} : {}\n\n'.format(cwd,
                                                         htmlBuilderCommandLineStr))

            # Run Sphinx, reading stdout in a separate thread.
            self._qe = QEventLoop()
            # Sphinx will output just a carriage return (0x0D) to simulate a
            # single line being updated by build status and the build
            # progresses. Without universal newline support here, we'll wait
            # until the build is complete (with a \n\r) to report any build
            # progress! So, enable universal newlines, so that each \r will be
            # treated as a separate line, providing immediate feedback on build
            # progress.
            popen = open_console_output(htmlBuilderCommandLine, cwd=cwd,
                                        universal_newlines=True)
            # Perform reads in an event loop. The loop is exit when all reads
            # have completed. We can't simply start the _stderr_read thread
            # here, because calls to self._qe_exit() will be ignored until
            # we're inside the event loop.
            QTimer.singleShot(0, lambda: self._popen_read(popen))
            self._qe.exec_()
        except OSError as ex:
            return (
                'Failed to execute HTML builder:\n'
                '{}\n'.format(str(ex)) +
                'Go to Settings -> Settings -> CodeChat to set HTML'
                ' builder configurations.')

        return self._stderr

    # Read from stdout (in this thread) and stderr (in another thread),
    # so that the user sees output as the build progresses, rather than only
    # producing output after the build is complete.
    def _popen_read(self, popen):
        # Read are blocking; we can't read from both stdout and stderr in the
        # same thread without possible buffer overflows. So, use this thread to
        # read from and immediately report progress from stdout. In another
        # thread, read all stderr and report that after the build finishes.
        self._ac.start(None, self._stderr_read, popen.stderr)

        # Read a line of stdout then report it to the user immediately.
        s = popen.stdout.readline()
        while s:
            self.logWindowText.emit(s.rstrip('\n'))
            s = popen.stdout.readline()
        self._SphinxInvocationCount += 1
        # I would expect the following code to do the same thing. It doesn't:
        # instead, it waits until Sphinx completes before returning anything.
        # ???
        #
        # .. code-block: python
        #    :linenos:
        #
        #    for s in popen.stdout:
        #        self.logWindowText.emit(s)

    # Runs in a separate thread to read stdout. It then exits the QEventLoop as
    # a way to signal that stderr reads have completed.
    def _stderr_read(self, stderr):
        self._stderr = stderr.read()
        self._qe.exit()

    def run(self):
        """Thread function
        """
        while True:  # exits with break
            # wait task
            task = self._queue.get()
            # take the last task
            while self._queue.qsize():
                task = self._queue.get()

            if task is None:  # None is a quit command
                self._ac.terminate()
                break

            # TODO: This is ugly. Should pass this exception back to the main
            # thread and re-raise it there, or use a QFuture like approach which
            # does this automaticlaly.
            try:
                html, errString, url = self._getHtml(task.language, task.text,
                                                     task.filePath)
            except Exception:
                traceback.print_exc()

            self.htmlReady.emit(task.filePath, html, errString, url)

        # Free resources.
        self._ac.terminate()
Пример #18
0
 def test_2(self):
     for _ in self.syncPoolAndThread:
         with AsyncController(_) as ac:
             em = Emitter(2, self.assertEqual)
             with WaitForSignal(em.bing, 1000):
                 ac.start(em.g, lambda: 2)
Пример #19
0
    def test_18(self):
        for _ in self.syncPoolAndThread:
            # Terminate using a context manager.
            with AsyncController(_) as ac1:
                pass
            with self.assertRaises(AssertionError):
                ac1.start(None, lambda: None)

            # Termiante by calling terminate.
            ac2 = AsyncController(_)
            ac2.terminate()
            with self.assertRaises(AssertionError):
                ac2.start(None, lambda: None)

            # Terminate via __del__, I hope.
            ac3 = AsyncController(_)
            del ac3
            # Can't try start, since the object was deleted.

            # Terminate via the QT object tree.
            o = QObject()
            ac3 = AsyncController(_, o)
            sip.delete(o)
            with self.assertRaises(AssertionError):
                ac3.start(None, lambda: None)
Пример #20
0
class SphinxConverter(QObject):
    """This class converts Sphinx input to HTML. It is run in a separate
    thread.
    """
    # This signal clears the context of the log window.
    logWindowClear = pyqtSignal()

    # This signal emits messages for the log window.
    logWindowText = pyqtSignal(
        # A string to append to the log window.
        str)

    def __init__(self, parent):
        super().__init__(parent)
        # Use an additional thread to process Sphinx output.
        self._ac = AsyncController('QThread', self)
        self._ac.defaultPriority = QThread.LowPriority
        self._SphinxInvocationCount = 1

    def terminate(self):
        # Free resources.
        self._ac.terminate()

    def convert(self, filePath):
        # Run the builder.
        errString = self._runHtmlBuilder()

        # Look for the HTML output.
        #
        # Get an absolute path to the output path, which could be relative.
        outputPath = core.config()['Sphinx']['OutputPath']
        projectPath = core.config()['Sphinx']['ProjectPath']
        if not os.path.isabs(outputPath):
            outputPath = os.path.join(projectPath, outputPath)
        # Create an htmlPath as OutputPath + remainder of filePath.
        htmlPath = os.path.join(outputPath + filePath[len(projectPath):])
        html_file_suffix = '.html'
        try:
            with codecs.open(os.path.join(projectPath,
                                          'sphinx-enki-info.txt')) as f:
                hfs = f.read()
                # If the file is empty, then html_file_suffix wasn't defined
                # or is None. In this case, use the default extension.
                # Otherwise, use the extension read from the file.
                if hfs:
                    html_file_suffix = hfs
        except:
            errString = "Warning: assuming .html extension. Use " + \
                "the conf.py template to set the extension.\n" + errString
            pass
        # First place to look: file.html. For example, look for foo.py
        # in foo.py.html.
        htmlFile = htmlPath + html_file_suffix
        # Second place to look: file without extension.html. For
        # example, look for foo.html for foo.rst.
        htmlFileAlter = os.path.splitext(htmlPath)[0] + html_file_suffix
        # Check that the output file produced by Sphinx is newer than
        # the source file it was built from.
        if os.path.exists(htmlFile):
            return _checkModificationTime(filePath, htmlFile, errString)
        elif os.path.exists(htmlFileAlter):
            return _checkModificationTime(filePath, htmlFileAlter, errString)
        else:
            return (filePath,
                    'No preview for this type of file.<br>Expected ' +
                    htmlFile + " or " + htmlFileAlter, errString, QUrl())

    def _runHtmlBuilder(self):
        # Build the commond line for Sphinx.
        if core.config()['Sphinx']['AdvancedMode']:
            htmlBuilderCommandLine = core.config()['Sphinx']['Cmdline']
            if sys.platform.startswith('linux'):
                # If Linux is used, then subprocess cannot take the whole
                # commandline as the name of an executable file. Module shlex
                # has to be used to parse commandline.
                htmlBuilderCommandLine = shlex.split(htmlBuilderCommandLine)
        else:
            # For available builder options, refer to: http://sphinx-doc.org/builders.html
            htmlBuilderCommandLine = [
                core.config()['Sphinx']['Executable'],
                # Place doctrees in the ``_build`` directory; by default, Sphinx
                # places this in _build/html/.doctrees.
                '-d',
                os.path.join('_build', 'doctrees'),
                # Source directory -- the current directory, since we'll chdir to
                # the project directory before executing this.
                '.',
                # Build directory
                core.config()['Sphinx']['OutputPath']
            ]

        # Invoke it.
        try:
            # Clear the log at the beginning of a Sphinx build.
            self.logWindowClear.emit()

            cwd = core.config()['Sphinx']['ProjectPath']
            # If the command line is already a string (advanced mode), just print it.
            # Otherwise, it's a list that should be transformed to a string.
            if isinstance(htmlBuilderCommandLine, str):
                htmlBuilderCommandLineStr = htmlBuilderCommandLine
            else:
                htmlBuilderCommandLineStr = ' '.join(htmlBuilderCommandLine)
            self.logWindowText.emit('{} : {}\n\n'.format(
                cwd, htmlBuilderCommandLineStr))

            # Run Sphinx, reading stdout in a separate thread.
            self._qe = QEventLoop()
            # Sphinx will output just a carriage return (0x0D) to simulate a
            # single line being updated by build status and the build
            # progresses. Without universal newline support here, we'll wait
            # until the build is complete (with a \n\r) to report any build
            # progress! So, enable universal newlines, so that each \r will be
            # treated as a separate line, providing immediate feedback on build
            # progress.
            popen = open_console_output(htmlBuilderCommandLine,
                                        cwd=cwd,
                                        universal_newlines=True)
            # Perform reads in an event loop. The loop is exit when all reads
            # have completed. We can't simply start the _stderr_read thread
            # here, because calls to self._qe_exit() will be ignored until
            # we're inside the event loop.
            QTimer.singleShot(0, lambda: self._popen_read(popen))
            self._qe.exec_()
        except OSError as ex:
            return ('Failed to execute HTML builder:\n'
                    '{}\n'.format(str(ex)) +
                    'Go to Settings -> Settings -> CodeChat to set HTML'
                    ' builder configurations.')

        return self._stderr

    # Read from stdout (in this thread) and stderr (in another thread),
    # so that the user sees output as the build progresses, rather than only
    # producing output after the build is complete.
    def _popen_read(self, popen):
        # Read are blocking; we can't read from both stdout and stderr in the
        # same thread without possible buffer overflows. So, use this thread to
        # read from and immediately report progress from stdout. In another
        # thread, read all stderr and report that after the build finishes.
        self._ac.start(None, self._stderr_read, popen.stderr)

        # Read a line of stdout then report it to the user immediately.
        s = popen.stdout.readline()
        while s:
            self.logWindowText.emit(s.rstrip('\n'))
            s = popen.stdout.readline()
        self._SphinxInvocationCount += 1
        # I would expect the following code to do the same thing. It doesn't:
        # instead, it waits until Sphinx completes before returning anything.
        # ???
        #
        # .. code-block: python
        #    :linenos:
        #
        #    for s in popen.stdout:
        #        self.logWindowText.emit(s)

    # Runs in a separate thread to read stdout. It then exits the QEventLoop as
    # a way to signal that stderr reads have completed.
    def _stderr_read(self, stderr):
        self._stderr = stderr.read()
        self._qe.exit()
Пример #21
0
 def __init__(self, parent):
     super().__init__(parent)
     # Use an additional thread to process Sphinx output.
     self._ac = AsyncController('QThread', self)
     self._ac.defaultPriority = QThread.LowPriority
     self._SphinxInvocationCount = 1
Пример #22
0
 def __init__(self, parent):
     super().__init__(parent)
     # Use an additional thread to process Sphinx output.
     self._ac = AsyncController('QThread', self)
     self._ac.defaultPriority = QThread.LowPriority
     self._SphinxInvocationCount = 1
Пример #23
0
class SphinxConverter(QObject):
    """This class converts Sphinx input to HTML. It is run in a separate
    thread.
    """
    # This signal clears the context of the log window.
    logWindowClear = pyqtSignal()

    # This signal emits messages for the log window.
    logWindowText = pyqtSignal(
      # A string to append to the log window.
      str)

    def __init__(self, parent):
        super().__init__(parent)
        # Use an additional thread to process Sphinx output.
        self._ac = AsyncController('QThread', self)
        self._ac.defaultPriority = QThread.LowPriority
        self._SphinxInvocationCount = 1

    def terminate(self):
        # Free resources.
        self._ac.terminate()

    def convert(self, filePath):
        # Run the builder.
        errString = self._runHtmlBuilder()

        # Look for the HTML output.
        #
        # Get an absolute path to the output path, which could be relative.
        outputPath = core.config()['Sphinx']['OutputPath']
        projectPath = core.config()['Sphinx']['ProjectPath']
        if not os.path.isabs(outputPath):
            outputPath = os.path.join(projectPath, outputPath)
        # Create an htmlPath as OutputPath + remainder of filePath.
        htmlPath = os.path.join(outputPath + filePath[len(projectPath):])
        html_file_suffix = '.html'
        try:
            with codecs.open(os.path.join(projectPath, 'sphinx-enki-info.txt')) as f:
                hfs = f.read()
                # If the file is empty, then html_file_suffix wasn't defined
                # or is None. In this case, use the default extension.
                # Otherwise, use the extension read from the file.
                if hfs:
                    html_file_suffix = hfs
        except:
            errString = "Warning: assuming .html extension. Use " + \
                "the conf.py template to set the extension.\n" + errString
            pass
        # First place to look: file.html. For example, look for foo.py
        # in foo.py.html.
        htmlFile = htmlPath + html_file_suffix
        # Second place to look: file without extension.html. For
        # example, look for foo.html for foo.rst.
        htmlFileAlter = os.path.splitext(htmlPath)[0] + html_file_suffix
        # Check that the output file produced by Sphinx is newer than
        # the source file it was built from.
        if os.path.exists(htmlFile):
            return _checkModificationTime(filePath, htmlFile, errString)
        elif os.path.exists(htmlFileAlter):
            return _checkModificationTime(filePath, htmlFileAlter, errString)
        else:
            return (filePath, 'No preview for this type of file.<br>Expected ' +
                    htmlFile + " or " + htmlFileAlter, errString, QUrl())

    def _runHtmlBuilder(self):
        # Build the commond line for Sphinx.
        if core.config()['Sphinx']['AdvancedMode']:
            htmlBuilderCommandLine = core.config()['Sphinx']['Cmdline']
            if sys.platform.startswith('linux'):
                # If Linux is used, then subprocess cannot take the whole
                # commandline as the name of an executable file. Module shlex
                # has to be used to parse commandline.
                htmlBuilderCommandLine = shlex.split(htmlBuilderCommandLine)
        else:
            # For available builder options, refer to: http://sphinx-doc.org/builders.html
            htmlBuilderCommandLine = [core.config()['Sphinx']['Executable'],
              # Place doctrees in the ``_build`` directory; by default, Sphinx
              # places this in _build/html/.doctrees.
              '-d', os.path.join('_build', 'doctrees'),
              # Source directory -- the current directory, since we'll chdir to
              # the project directory before executing this.
              '.',
              # Build directory
              core.config()['Sphinx']['OutputPath']]

        # Invoke it.
        try:
            # Clear the log at the beginning of a Sphinx build.
            self.logWindowClear.emit()

            cwd = core.config()['Sphinx']['ProjectPath']
            # If the command line is already a string (advanced mode), just print it.
            # Otherwise, it's a list that should be transformed to a string.
            if isinstance(htmlBuilderCommandLine, str):
                htmlBuilderCommandLineStr = htmlBuilderCommandLine
            else:
                htmlBuilderCommandLineStr = ' '.join(htmlBuilderCommandLine)
            self.logWindowText.emit('{} : {}\n\n'.format(cwd,
                                                         htmlBuilderCommandLineStr))

            # Run Sphinx, reading stdout in a separate thread.
            self._qe = QEventLoop()
            # Sphinx will output just a carriage return (0x0D) to simulate a
            # single line being updated by build status and the build
            # progresses. Without universal newline support here, we'll wait
            # until the build is complete (with a \n\r) to report any build
            # progress! So, enable universal newlines, so that each \r will be
            # treated as a separate line, providing immediate feedback on build
            # progress.
            popen = open_console_output(htmlBuilderCommandLine, cwd=cwd,
                                        universal_newlines=True)
            # Perform reads in an event loop. The loop is exit when all reads
            # have completed. We can't simply start the _stderr_read thread
            # here, because calls to self._qe_exit() will be ignored until
            # we're inside the event loop.
            QTimer.singleShot(0, lambda: self._popen_read(popen))
            self._qe.exec_()
        except OSError as ex:
            return (
                'Failed to execute HTML builder:\n'
                '{}\n'.format(str(ex)) +
                'Go to Settings -> Settings -> CodeChat to set HTML'
                ' builder configurations.')

        return self._stderr

    # Read from stdout (in this thread) and stderr (in another thread),
    # so that the user sees output as the build progresses, rather than only
    # producing output after the build is complete.
    def _popen_read(self, popen):
        # Read are blocking; we can't read from both stdout and stderr in the
        # same thread without possible buffer overflows. So, use this thread to
        # read from and immediately report progress from stdout. In another
        # thread, read all stderr and report that after the build finishes.
        self._ac.start(None, self._stderr_read, popen.stderr)

        # Read a line of stdout then report it to the user immediately.
        s = popen.stdout.readline()
        while s:
            self.logWindowText.emit(s.rstrip('\n'))
            s = popen.stdout.readline()
        self._SphinxInvocationCount += 1
        # I would expect the following code to do the same thing. It doesn't:
        # instead, it waits until Sphinx completes before returning anything.
        # ???
        #
        # .. code-block: python
        #    :linenos:
        #
        #    for s in popen.stdout:
        #        self.logWindowText.emit(s)

    # Runs in a separate thread to read stdout. It then exits the QEventLoop as
    # a way to signal that stderr reads have completed.
    def _stderr_read(self, stderr):
        self._stderr = stderr.read()
        self._qe.exit()