From d12961a9ed746ed6c63ff443eabab865401ff547 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 20 Apr 2025 16:02:16 +0200 Subject: [PATCH 1/4] Fix more documents link in documentation --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 00a680b07..3cefdc4e2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -41,7 +41,7 @@ See :ref:`a_started` for more details. :hidden: Main Page - More Documents + More Documents .. toctree:: :maxdepth: 1 From c5915664f0dd0a83041590c4c91aac71e2814171 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 20 Apr 2025 16:02:58 +0200 Subject: [PATCH 2/4] Add compression to sample.zip for assets, see #907 --- utils/assets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/assets.py b/utils/assets.py index b9291c745..c9195b3a0 100644 --- a/utils/assets.py +++ b/utils/assets.py @@ -90,7 +90,9 @@ def buildSampleZip(args: argparse.Namespace | None = None) -> None: if srcSample.is_dir(): dstSample.unlink(missing_ok=True) - with zipfile.ZipFile(dstSample, "w") as zipObj: + with zipfile.ZipFile( + dstSample, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=3 + ) as zipObj: print("Compressing: nwProject.nwx") zipObj.write(srcSample / "nwProject.nwx", "nwProject.nwx") for doc in (srcSample / "content").iterdir(): From f15ed321018e431b92abca7c781bc4076d8ad4ef Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:18:07 +0200 Subject: [PATCH 3/4] Update build scripts for docs --- .github/workflows/build_assets.yml | 2 +- docs/source/tech_source.rst | 9 +- novelwriter/config.py | 3 +- pkgutils.py | 34 ++++--- utils/assets.py | 128 +----------------------- utils/docs.py | 151 +++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 148 deletions(-) create mode 100644 utils/docs.py diff --git a/.github/workflows/build_assets.yml b/.github/workflows/build_assets.yml index 6b252624c..26f9eacee 100644 --- a/.github/workflows/build_assets.yml +++ b/.github/workflows/build_assets.yml @@ -32,7 +32,7 @@ jobs: with: name: nw-assets path: | - novelwriter/assets/manual.pdf + novelwriter/assets/manual*.pdf novelwriter/assets/sample.zip novelwriter/assets/i18n/*.qm if-no-files-found: error diff --git a/docs/source/tech_source.rst b/docs/source/tech_source.rst index 935214162..6e0fc62b6 100644 --- a/docs/source/tech_source.rst +++ b/docs/source/tech_source.rst @@ -153,8 +153,9 @@ You can also build a PDF manual from the documentation using the ``pkgutils.py`` .. code-block:: bash - python pkgutils.py manual + python pkgutils.py docs-pdf en -This will build the documentation as a PDF using LaTeX. The file will then be copied into the -assets folder and made available in the **Help** menu in novelWriter. The Sphinx build system has a -few extra dependencies when building the PDF. Please check the `Sphinx Docs`_ for more details. +This will build the English documentation as a PDF using LaTeX. The file will then be copied into +the assets folder and made available in the **Help** menu in novelWriter. Replace ``en`` with +``all`` to build for all languages. The Sphinx build system has a few extra dependencies when +building the PDF. Please check the `Sphinx Docs`_ for more details. diff --git a/novelwriter/config.py b/novelwriter/config.py index 51d6e1646..c6dbb47b8 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -303,7 +303,8 @@ def hasError(self) -> bool: @property def pdfDocs(self) -> Path | None: - return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual")) + """Return the local manual PDF file, if any exist.""" + return self._manuals.get(f"manual_{self.locale.bcp47Name()}", self._manuals.get("manual")) @property def nwLangPath(self) -> Path: diff --git a/pkgutils.py b/pkgutils.py index 8f72d8d11..fc30b985b 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -37,6 +37,7 @@ import utils.build_binary import utils.build_debian import utils.build_windows +import utils.docs import utils.icon_themes from utils.common import ROOT_DIR, SETUP_DIR, extractVersion, readFile, stripVersion, writeFile @@ -88,13 +89,13 @@ def cleanBuildDirs(args: argparse.Namespace) -> None: print("") folders = [ - ROOT_DIR / "build", ROOT_DIR / "build_bin", - ROOT_DIR / "dist", + ROOT_DIR / "build", + ROOT_DIR / "dist_appimage", ROOT_DIR / "dist_bin", ROOT_DIR / "dist_deb", - ROOT_DIR / "dist_minimal", - ROOT_DIR / "dist_appimage", + ROOT_DIR / "dist_doc", + ROOT_DIR / "dist", ROOT_DIR / "novelWriter.egg-info", ] @@ -211,20 +212,21 @@ def genMacOSPlist(args: argparse.Namespace) -> None: ) ) cmdUpdateDocsPo.add_argument("lang", nargs="+") - cmdUpdateDocsPo.set_defaults(func=utils.assets.updateDocsTranslationSources) + cmdUpdateDocsPo.set_defaults(func=utils.docs.updateDocsTranslationSources) - # Build Docs i18n Files - cmdBuildU18nDocs = parsers.add_parser( - "docs-lrelease", help="Build the translated PDF manual files." + # Build PDF Docs + cmdBuildPdfDocs = parsers.add_parser( + "docs-pdf", help="Build the PDF manual files." ) - cmdBuildU18nDocs.add_argument("lang", nargs="+") - cmdBuildU18nDocs.set_defaults(func=utils.assets.buildDocsTranslationAssets) + cmdBuildPdfDocs.add_argument("lang", nargs="+") + cmdBuildPdfDocs.set_defaults(func=utils.docs.buildPdfDocAssets) - # Build Manual - cmdBuildManual = parsers.add_parser( - "manual", help="Build the help documentation as a PDF (requires LaTeX)." + # Build HTML Docs + cmdBuildHtmlDocs = parsers.add_parser( + "docs-html", help="Build the HTML docs." ) - cmdBuildManual.set_defaults(func=utils.assets.buildPdfManual) + cmdBuildHtmlDocs.add_argument("lang", nargs="+") + cmdBuildHtmlDocs.set_defaults(func=utils.docs.buildHtmlDocs) # Build Sample cmdBuildSample = parsers.add_parser( @@ -234,13 +236,13 @@ def genMacOSPlist(args: argparse.Namespace) -> None: # Clean Assets cmdCleanAssets = parsers.add_parser( - "clean-assets", help="Delete assets built by manual, sample and qtlrelease." + "clean-assets", help="Delete assets built by docs-pdf, sample and qtlrelease." ) cmdCleanAssets.set_defaults(func=utils.assets.cleanBuiltAssets) # Build Assets cmdBuildAssets = parsers.add_parser( - "build-assets", help="Build all assets. Includes manual, sample and qtlrelease." + "build-assets", help="Build all assets. Includes docs-pdf, sample and qtlrelease." ) cmdBuildAssets.set_defaults(func=utils.assets.buildAllAssets) diff --git a/utils/assets.py b/utils/assets.py index c9195b3a0..1849c6962 100644 --- a/utils/assets.py +++ b/utils/assets.py @@ -21,7 +21,6 @@ from __future__ import annotations import argparse -import os import subprocess import sys import zipfile @@ -29,51 +28,7 @@ from pathlib import Path from utils.common import ROOT_DIR, writeFile - - -def buildPdfManual(args: argparse.Namespace | None = None) -> None: - """This function will build the documentation as manual.pdf.""" - print("") - print("Building PDF Manual") - print("===================") - print("") - - buildFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf" - finalFile = ROOT_DIR / "novelwriter" / "assets" / "manual.pdf" - finalFile.unlink(missing_ok=True) - - try: - subprocess.call(["make", "clean"], cwd="docs") - exCode = subprocess.call(["make", "latexpdf"], cwd="docs") - if exCode == 0: - print("") - buildFile.rename(finalFile) - else: - raise Exception(f"Build returned error code {exCode}") - - print("PDF manual build: OK") - print("") - - except Exception as exc: - print("PDF manual build: FAILED") - print("") - print(str(exc)) - print("") - print("Dependencies:") - print(" * pip install sphinx") - print(" * Package latexmk") - print(" * LaTeX build system") - print("") - print(" On Debian/Ubuntu, install: python3-sphinx latexmk texlive texlive-latex-extra") - print("") - sys.exit(1) - - if not finalFile.is_file(): - print("No output file was found!") - print("") - sys.exit(1) - - return +from utils.docs import buildPdfDocAssets def buildSampleZip(args: argparse.Namespace | None = None) -> None: @@ -251,84 +206,6 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None: return -def updateDocsTranslationSources(args: argparse.Namespace) -> None: - """Build the documentation .po files.""" - print("") - print("Building Docs Translation Files") - print("===============================") - print("") - - docsDir = ROOT_DIR / "docs" - locsDir = ROOT_DIR / "docs" / "source" / "locales" - locsDir.mkdir(exist_ok=True) - - print("Generating POT Files") - subprocess.call(["make", "gettext"], cwd=docsDir) - print("") - - lang = args.lang - update = [] - if lang == ["all"]: - update = [i.stem for i in locsDir.iterdir() if i.is_dir()] - else: - update = lang - - print("Generating PO Files") - print("Languages: ", update) - print("") - - for code in update: - subprocess.call(["sphinx-intl", "update", "-p", "build/gettext", "-l", code], cwd=docsDir) - print("") - - print("Done") - print("") - - return - - -def buildDocsTranslationAssets(args: argparse.Namespace | None = None) -> None: - """Build the documentation i18n PDF files.""" - from PyQt6.QtCore import QLocale - - print("") - print("Building Docs Manuals") - print("=====================") - print("") - - docsDir = ROOT_DIR / "docs" - locsDir = ROOT_DIR / "docs" / "source" / "locales" - pdfFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf" - locsDir.mkdir(exist_ok=True) - - lang = args.lang if args else ["all"] - build = [] - if lang == ["all"]: - build = [i.stem for i in locsDir.iterdir() if i.is_dir()] - else: - build = lang - - for code in build: - data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8") - authors = [x for x in data.splitlines() if x and not x.startswith("#")] - env = os.environ.copy() - env["SPHINX_I18N_AUTHORS"] = ", ".join(authors) - exCode = subprocess.call( - f"make -e SPHINXOPTS=\"-D language='{code}'\" clean latexpdf", - cwd=docsDir, env=env, shell=True - ) - if exCode == 0: - print("") - name = f"manual_{QLocale(code).name()}.pdf" - pdfFile.rename(ROOT_DIR / "novelwriter" / "assets" / name) - else: - raise Exception(f"Build returned error code {exCode}") - - print("") - - return - - def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None: """Remove assets built by this script.""" print("") @@ -352,8 +229,7 @@ def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None: def buildAllAssets(args: argparse.Namespace) -> None: """Build all assets.""" cleanBuiltAssets() - buildPdfManual() buildSampleZip() buildTranslationAssets() - buildDocsTranslationAssets() + buildPdfDocAssets() return diff --git a/utils/docs.py b/utils/docs.py new file mode 100644 index 000000000..4d0360756 --- /dev/null +++ b/utils/docs.py @@ -0,0 +1,151 @@ +""" +novelWriter – Documentation +=========================== + +This file is a part of novelWriter +Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +import argparse +import os +import shutil +import subprocess + +from utils.common import ROOT_DIR + + +def updateDocsTranslationSources(args: argparse.Namespace) -> None: + """Build the documentation .po files.""" + print("") + print("Building Docs Translation Files") + print("===============================") + print("") + + docsDir = ROOT_DIR / "docs" + locsDir = ROOT_DIR / "docs" / "source" / "locales" + locsDir.mkdir(exist_ok=True) + + print("Generating POT Files") + subprocess.call(["make", "gettext"], cwd=docsDir) + print("") + + lang = args.lang + update = [] + if lang == ["all"]: + update = [i.stem for i in locsDir.iterdir() if i.is_dir()] + else: + update = lang + + print("Generating PO Files") + print("Languages: ", update) + print("") + + for code in update: + subprocess.call(["sphinx-intl", "update", "-p", "build/gettext", "-l", code], cwd=docsDir) + print("") + + print("Done") + print("") + + return + + +def buildHtmlDocs(args: argparse.Namespace | None = None) -> None: + """Build the documentation files.""" + + print("") + print("Building HTML Docs") + print("==================") + print("") + + bldRoot = ROOT_DIR / "dist_doc" + docsDir = ROOT_DIR / "docs" + locsDir = ROOT_DIR / "docs" / "source" / "locales" + locsDir.mkdir(exist_ok=True) + bldRoot.mkdir(exist_ok=True) + + lang = args.lang if args else ["all"] + build = [] + if lang == ["all"]: + build = ["en"] + [i.stem for i in locsDir.iterdir() if i.is_dir()] + else: + build = lang + + for code in build: + outDir = bldRoot / code + env = os.environ.copy() + cmd = "make clean html" + if code != "en": + data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8") + authors = [x for x in data.splitlines() if x and not x.startswith("#")] + env["SPHINX_I18N_AUTHORS"] = ", ".join(authors) + cmd += f" -e SPHINXOPTS=\"-D language='{code}'\"" + + if (ex := subprocess.call(cmd, cwd=docsDir, env=env, shell=True)) == 0: + print("") + if outDir.exists(): + shutil.rmtree(outDir) + outDir.mkdir() + (docsDir / "build" / "html").rename(outDir / "html") + else: + raise Exception(f"Build returned error code {ex}") + + print("") + + return + + +def buildPdfDocAssets(args: argparse.Namespace | None = None) -> None: + """Build the documentation PDF files.""" + + print("") + print("Building Docs Manuals") + print("=====================") + print("") + + docsDir = ROOT_DIR / "docs" + locsDir = ROOT_DIR / "docs" / "source" / "locales" + pdfFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf" + locsDir.mkdir(exist_ok=True) + + lang = args.lang if args else ["all"] + build = [] + if lang == ["all"]: + build = ["en"] + [i.stem for i in locsDir.iterdir() if i.is_dir()] + else: + build = lang + + for code in build: + env = os.environ.copy() + cmd = "make clean latexpdf" + name = "manual.pdf" + if code != "en": + data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8") + authors = [x for x in data.splitlines() if x and not x.startswith("#")] + env["SPHINX_I18N_AUTHORS"] = ", ".join(authors) + cmd += f" -e SPHINXOPTS=\"-D language='{code}'\"" + name = f"manual_{code}.pdf" + + if (ex := subprocess.call(cmd, cwd=docsDir, env=env, shell=True)) == 0: + print("") + pdfFile.rename(ROOT_DIR / "novelwriter" / "assets" / name) + else: + raise Exception(f"Build returned error code {ex}") + + print("") + + return From 529b93f1541980dfe614e7db39092302b29c2608 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 20 Apr 2025 17:21:42 +0200 Subject: [PATCH 4/4] Change the output folder structure for HTML docs --- utils/docs.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/utils/docs.py b/utils/docs.py index 4d0360756..c43d3f374 100644 --- a/utils/docs.py +++ b/utils/docs.py @@ -72,11 +72,11 @@ def buildHtmlDocs(args: argparse.Namespace | None = None) -> None: print("==================") print("") - bldRoot = ROOT_DIR / "dist_doc" + bldRoot = ROOT_DIR / "dist_doc" / "html" docsDir = ROOT_DIR / "docs" locsDir = ROOT_DIR / "docs" / "source" / "locales" locsDir.mkdir(exist_ok=True) - bldRoot.mkdir(exist_ok=True) + bldRoot.mkdir(exist_ok=True, parents=True) lang = args.lang if args else ["all"] build = [] @@ -99,8 +99,7 @@ def buildHtmlDocs(args: argparse.Namespace | None = None) -> None: print("") if outDir.exists(): shutil.rmtree(outDir) - outDir.mkdir() - (docsDir / "build" / "html").rename(outDir / "html") + (docsDir / "build" / "html").rename(outDir) else: raise Exception(f"Build returned error code {ex}")