Skip to content

Instantly share code, notes, and snippets.

@MagicSword
Forked from vsajip/doc-watch.py
Created January 27, 2012 01:42
Show Gist options
  • Save MagicSword/1686397 to your computer and use it in GitHub Desktop.
Save MagicSword/1686397 to your computer and use it in GitHub Desktop.

Revisions

  1. @vsajip vsajip created this gist Jan 24, 2012.
    134 changes: 134 additions & 0 deletions doc-watch.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    #!/usr/bin/env python
    #
    # Copyright (C) 2012 Vinay Sajip. Licensed under the MIT license.
    #
    # Based on Roberto Alsina's 128-line web browser, see
    #
    # http://lateral.netmanagers.com.ar/weblog/posts/BB948.html
    #
    import json
    import os
    import subprocess
    import sys
    import tempfile
    from urllib import pathname2url

    import sip
    sip.setapi("QString", 2)
    sip.setapi("QVariant", 2)

    from PyQt4 import QtGui,QtCore,QtWebKit, QtNetwork

    settings = QtCore.QSettings("Vinay Sajip", "DocWatch")

    class Watcher(QtCore.QThread):
    """
    A watcher which looks for source file changes, builds the documentation,
    and notifies the browser to refresh its contents
    """
    def run(self):
    self._stop = False
    watch_command = 'inotifywait -rq -e close_write --exclude \'"*.html"\' .'.split()
    make_command = 'make html'.split()
    while not self._stop:
    # Perhaps should put notifier access in a mutex - not bothering yet
    self.notifier = subprocess.Popen(watch_command)
    self.notifier.wait()
    if self._stop:
    break
    subprocess.call(make_command)
    # Refresh the UI ...
    self.parent().changed.emit()

    def stop(self):
    self._stop = True
    # Perhaps should put notifier access in a mutex - not bothering for now
    if self.notifier.poll() is None: # not yet terminated ...
    self.notifier.terminate()

    class MainWindow(QtGui.QMainWindow):
    """
    A browser intended for viewing HTML documentation generated by Sphinx.
    """
    changed = QtCore.pyqtSignal()

    def __init__(self, url):
    QtGui.QMainWindow.__init__(self)
    self.sb=self.statusBar()

    self.pbar = QtGui.QProgressBar()
    self.pbar.setMaximumWidth(120)
    self.wb=QtWebKit.QWebView(loadProgress = self.pbar.setValue, loadFinished = self.pbar.hide, loadStarted = self.pbar.show, titleChanged = self.setWindowTitle)
    self.setCentralWidget(self.wb)

    self.tb=self.addToolBar("Main Toolbar")
    for a in (QtWebKit.QWebPage.Back, QtWebKit.QWebPage.Forward, QtWebKit.QWebPage.Reload):
    self.tb.addAction(self.wb.pageAction(a))

    self.url = QtGui.QLineEdit(returnPressed = lambda:self.wb.setUrl(QtCore.QUrl.fromUserInput(self.url.text())))
    self.tb.addWidget(self.url)

    self.wb.urlChanged.connect(lambda u: self.url.setText(u.toString()))
    self.wb.urlChanged.connect(lambda: self.url.setCompleter(QtGui.QCompleter(QtCore.QStringList([QtCore.QString(i.url().toString()) for i in self.wb.history().items()]), caseSensitivity = QtCore.Qt.CaseInsensitive)))

    self.wb.statusBarMessage.connect(self.sb.showMessage)
    self.wb.page().linkHovered.connect(lambda l: self.sb.showMessage(l, 3000))

    self.search = QtGui.QLineEdit(returnPressed = lambda: self.wb.findText(self.search.text()))
    self.search.hide()
    self.showSearch = QtGui.QShortcut("Ctrl+F", self, activated = lambda: (self.search.show() , self.search.setFocus()))
    self.hideSearch = QtGui.QShortcut("Esc", self, activated = lambda: (self.search.hide(), self.wb.setFocus()))

    self.quit = QtGui.QShortcut("Ctrl+Q", self, activated = self.close)
    self.zoomIn = QtGui.QShortcut("Ctrl++", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()+.2))
    self.zoomOut = QtGui.QShortcut("Ctrl+-", self, activated = lambda: self.wb.setZoomFactor(self.wb.zoomFactor()-.2))
    self.zoomOne = QtGui.QShortcut("Ctrl+=", self, activated = lambda: self.wb.setZoomFactor(1))
    self.wb.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)

    self.sb.addPermanentWidget(self.search)
    self.sb.addPermanentWidget(self.pbar)

    self.load_settings()

    self.wb.load(url)
    self.watcher = Watcher(self)

    self.changed.connect(self.wb.reload)

    self.watcher.start()

    def load_settings(self):
    settings.beginGroup('mainwindow')
    pos = settings.value('pos')
    size = settings.value('size')
    if isinstance(pos, QtCore.QPoint):
    self.move(pos)
    if isinstance(size, QtCore.QSize):
    self.resize(size)
    settings.endGroup()

    def save_settings(self):
    settings.beginGroup('mainwindow')
    settings.setValue('pos', self.pos())
    settings.setValue('size', self.size())
    settings.endGroup()

    def closeEvent(self, event):
    self.save_settings()
    self.watcher.stop()

    if __name__ == "__main__":
    if not os.path.isdir('_build'):
    # very simplistic sanity check. Works for me, as I generally use
    # sphinx-quickstart defaults
    print('You must run this application from a Sphinx directory containing _build')
    rc = 1
    else:
    app=QtGui.QApplication(sys.argv)
    path = os.path.join('_build', 'html', 'index.html')
    url = 'file:///' + pathname2url(os.path.abspath(path))
    url = QtCore.QUrl(url)
    wb=MainWindow(url)
    wb.show()
    rc = app.exec_()
    sys.exit(rc)