From eee0f5e4c928ab932fd56a601c7385884787f051 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 18 Aug 2021 00:13:28 +0200 Subject: [PATCH] Include HTML docs in minimal installs (#856) * Link to stable docs for stable release, otherwise latest docs * Add build command to setup for making HTML docs * Change the documentation menu entry to open either online or local HTML * Update docs build instructions in docs --- .gitignore | 1 + docs/source/int_started.rst | 21 +++---- nw/config.py | 18 +----- nw/gui/mainmenu.py | 95 ++++++++++++----------------- nw/guimain.py | 5 +- setup.py | 63 +++++++++++++++++++ tests/test_base/test_base_config.py | 10 --- 7 files changed, 111 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index 5ad24bbb..1b03d0dd 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ i18n/*.qph # Documentation /docs/build/ +/nw/assets/help/html/ *.qch *.qhc diff --git a/docs/source/int_started.rst b/docs/source/int_started.rst index cf48a76b..51384019 100644 --- a/docs/source/int_started.rst +++ b/docs/source/int_started.rst @@ -141,13 +141,12 @@ generated. It requires the following Python packages on Debian and Ubuntu. * ``python3-sphinx`` * ``python3-sphinx-rtd-theme`` -* ``python3-sphinxcontrib.qthelp`` Or from PyPi: .. code-block:: console - pip install sphinx sphinx-rtd-theme sphinxcontrib-qthelp + pip install sphinx sphinx-rtd-theme The documentation can then be built from the ``docs`` folder in the source code by running: @@ -158,19 +157,13 @@ The documentation can then be built from the ``docs`` folder in the source code If successful, the documentation should be available in the ``docs/build/html`` folder and you can open the ``index.html`` file in your browser. -The documentation can also be built for the Qt Assistant. To build the help packages from the -documentation source, run the following from the root source folder: +You can also build the documentation by using the setup script: .. code-block:: console - python setup.py qthelp + python setup.py docs -The setup script will copy the generated files into the ``nw/assets/help`` folder, and novelWriter -will detect the presence of the files and redirect the menu help entry to open help locally instead -of sending the user to the website. Pressing the :kbd:`F1` key will in any case try to open help -locally first, then send you to the website as a fallback. - -.. note:: - In order for the local version of help to work, the Qt Assistant must be installed on the local - computer. If it isn't available, or novelWriter cannot find it, the help feature will fall back - to redirecting you to the documentation website. +This does the same as the ``make help`` command, but in addition it copies the documentation into +novelWriter's assets folder. This will make it possible to open the documentation from inside of +novelWriter by pressing :kbd:`F1` even without an internet connection, as it will instead open the +local copy. diff --git a/nw/config.py b/nw/config.py index ccead708..031449fe 100644 --- a/nw/config.py +++ b/nw/config.py @@ -26,7 +26,6 @@ along with this program. If not, see . import os import sys import json -import shutil import logging from time import time @@ -72,11 +71,9 @@ class Config: self.themeRoot = None # The full path to the nw/assets/themes folder self.dictPath = None # The full path to the nw/assets/dict folder self.iconPath = None # The full path to the nw/assets/icons folder - self.helpPath = None # The full path to the novelwriter .qhc help file # Runtime Settings and Variables self.confChanged = False # True whenever the config has chenged, false after save - self.hasHelp = False # True if the Qt help files are present in the assets folder # General self.guiTheme = "default" @@ -230,8 +227,7 @@ class Config: self.kernelVer = "Unknown" # Packages - self.hasEnchant = False # The pyenchant package - self.hasAssistant = False # The Qt Assistant executable + self.hasEnchant = False # The pyenchant package # Recent Cache self.recentProj = {} @@ -360,11 +356,6 @@ class Config: if self.spellLanguage is None: self.spellLanguage = "en" - # Check if local help files exist - self.helpPath = os.path.join(self.assetPath, "help", "novelWriter.qhc") - self.hasHelp = os.path.isfile(self.helpPath) - self.hasHelp &= os.path.isfile(os.path.join(self.assetPath, "help", "novelWriter.qch")) - logger.debug("Config initialisation complete") return True @@ -930,13 +921,6 @@ class Config: self.hasEnchant = False logger.debug("Checking package 'pyenchant': Missing") - assistPath = shutil.which("assistant") - self.hasAssistant = assistPath is not None - if self.hasAssistant: - logger.debug("Checking executable 'assistant': OK") - else: - logger.debug("Checking executable 'assistant': Missing") - return # END Class Config diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 608c7c61..c80ffa46 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -23,10 +23,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import os import nw import logging -from PyQt5.QtCore import QUrl, QProcess +from http.client import HTTPSConnection +from urllib.parse import urljoin +from urllib.request import pathname2url + +from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMenuBar, QAction @@ -46,9 +51,6 @@ class GuiMainMenu(QMenuBar): self.theParent = theParent self.theProject = theParent.theProject - # Internals - self._assistProc = None - # Build Menu self._buildProjectMenu() self._buildDocumentMenu() @@ -89,25 +91,6 @@ class GuiMainMenu(QMenuBar): ) return - def closeHelp(self): - """Close the process used for the Qt Assistant, if it is open. - """ - if self._assistProc is None: - return - - if self._assistProc.state() == QProcess.Starting: - if self._assistProc.waitForStarted(10000): - self._assistProc.terminate() - else: - self._assistProc.kill() - - elif self._assistProc.state() == QProcess.Running: - self._assistProc.terminate() - if not self._assistProc.waitForFinished(10000): - self._assistProc.kill() - - return - ## # Update Menu on Settings Changed ## @@ -148,28 +131,38 @@ class GuiMainMenu(QMenuBar): self.theProject.setAutoOutline(theMode) return True - def _openAssistant(self, isChecked=False): - """Open the documentation in Qt Assistant. - """ - if not self.mainConf.hasHelp: - self._openWebsite(nw.__docurl__) - return False - - self._assistProc = QProcess(self) - self._assistProc.start("assistant", ["-collectionFile", self.mainConf.helpPath]) - - if not self._assistProc.waitForStarted(10000): - self._openWebsite(nw.__docurl__) - return False - - return True - def _openWebsite(self, theUrl): """Open a URL in the system's default browser. """ QDesktopServices.openUrl(QUrl(theUrl)) return True + def _openDocumentation(self): + """Open the documentation, and select whether it should be the + stable or latest version. If no internet connection, open + local. + """ + if nw.__hexversion__[-2] == "f": + docsPath = "/en/stable/" + else: + docsPath = "/en/latest/" + + try: + conn = HTTPSConnection(nw.__docurl__.replace("https://", "")) + conn.request("HEAD", docsPath) + resp = conn.getresponse() + hasAccess = resp.code == 200 + except Exception: + hasAccess = False + + docsFile = os.path.join(self.mainConf.assetPath, "help", "html", "index.html") + if hasAccess or not os.path.isfile(docsFile): + self._openWebsite(nw.__docurl__ + docsPath) + else: + self._openWebsite(urljoin("file:", pathname2url(docsFile))) + + return + ## # Menu Builders ## @@ -1113,24 +1106,12 @@ class GuiMainMenu(QMenuBar): # Help > Separator self.helpMenu.addSeparator() - # Document > Documentation - if self.mainConf.hasHelp and self.mainConf.hasAssistant: - self.aHelpLoc = QAction(self.tr("Documentation (Local)"), self) - self.aHelpLoc.setStatusTip(self.tr("View local documentation with Qt Assistant")) - self.aHelpLoc.triggered.connect(self._openAssistant) - self.aHelpLoc.setShortcut("F1") - self.helpMenu.addAction(self.aHelpLoc) - - self.aHelpWeb = QAction(self.tr("Documentation (Online)"), self) - self.aHelpWeb.setStatusTip( - self.tr("View online documentation at {0}").format(nw.__docurl__) - ) - self.aHelpWeb.triggered.connect(lambda: self._openWebsite(nw.__docurl__)) - if self.mainConf.hasHelp and self.mainConf.hasAssistant: - self.aHelpWeb.setShortcut("Shift+F1") - else: - self.aHelpWeb.setShortcuts(["F1", "Shift+F1"]) - self.helpMenu.addAction(self.aHelpWeb) + # Help > Documentation + self.aHelpDocs = QAction(self.tr("Documentation"), self) + self.aHelpDocs.setStatusTip(self.tr("Open documentation in browser")) + self.aHelpDocs.triggered.connect(self._openDocumentation) + self.aHelpDocs.setShortcut("F1") + self.helpMenu.addAction(self.aHelpDocs) # Help > Separator self.helpMenu.addSeparator() diff --git a/nw/guimain.py b/nw/guimain.py index 0e79a89e..add555f4 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -1190,7 +1190,6 @@ class GuiMain(QMainWindow): self.mainConf.saveConfig() self.reportConfErr() - self.mainMenu.closeHelp() qApp.quit() @@ -1373,9 +1372,7 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aPreferences) # Help - if self.mainConf.hasHelp and self.mainConf.hasAssistant: - self.addAction(self.mainMenu.aHelpLoc) - self.addAction(self.mainMenu.aHelpWeb) + self.addAction(self.mainMenu.aHelpDocs) return True diff --git a/setup.py b/setup.py index 238590b7..590c0f4a 100755 --- a/setup.py +++ b/setup.py @@ -221,6 +221,56 @@ def buildQtDocs(): return +## +# Html Documentation Builder (docs) +## + +def buildHtmlDocs(): + """This function will build the Sphinx HTML documentation. The files + are then copied into the nw/assets/help/html directory and can be + included in builds. + """ + buildDir = os.path.join("docs", "build", "html") + helpDir = os.path.join("nw", "assets", "help", "html") + + print("") + print("Building Documentation") + print("======================") + print("") + + buildFail = False + try: + subprocess.call(["make", "-C", "docs", "clean"]) + subprocess.call(["make", "-C", "docs", "html"]) + except Exception as e: + print("Docs Build Error:") + print(str(e)) + buildFail = True + + try: + if os.path.isdir(helpDir): + shutil.rmtree(helpDir) + shutil.copytree(buildDir, helpDir) + except Exception as e: + print("Docs Build Error:") + print(str(e)) + buildFail = True + + print("") + if buildFail: + print("Documentation build: FAILED") + print("") + print("Dependencies:") + print(" * pip install sphinx") + print(" * pip install sphinx-rtd-theme") + sys.exit(1) + else: + print("Documentation build: OK") + print("") + + return + + ## # Qt Linguist QM Builder (qtlrelease) ## @@ -349,6 +399,14 @@ def makeMinimalPackage(targetOS): print(str(e)) sys.exit(1) + # Build docs + try: + buildHtmlDocs() + except Exception as e: + print("Failed with error:") + print(str(e)) + sys.exit(1) + # Make translation files try: buildQtI18n() @@ -1137,6 +1195,7 @@ if __name__ == "__main__": "", "Additional Builds:", "", + " docs Build the help documentation as HTML." " qthelp Build the help documentation for use with the Qt Assistant.", " qtlupdate Update the translation files for internationalisation.", " qtlrelease Build the language files for internationalisation.", @@ -1203,6 +1262,10 @@ if __name__ == "__main__": # Additional Builds # ================= + if "docs" in sys.argv: + sys.argv.remove("docs") + buildHtmlDocs() + if "qthelp" in sys.argv: sys.argv.remove("qthelp") buildQtDocs() diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 6dff0402..9ac9c488 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -544,14 +544,4 @@ def testBaseConfig_Internal(monkeypatch, tmpConf): tmpConf._checkOptionalPackages() assert tmpConf.hasEnchant is False - with monkeypatch.context() as mp: - mp.setattr("shutil.which", lambda *a: "stuff") - tmpConf._checkOptionalPackages() - assert tmpConf.hasAssistant is True - - with monkeypatch.context() as mp: - mp.setattr("shutil.which", lambda *a: None) - tmpConf._checkOptionalPackages() - assert tmpConf.hasAssistant is False - # END Test testBaseConfig_Internal