diff --git a/.github/workflows/build_assets.yml b/.github/workflows/build_assets.yml index 6b252624..26f9eace 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/index.rst b/docs/source/index.rst index 00a680b0..3cefdc4e 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 diff --git a/docs/source/tech_source.rst b/docs/source/tech_source.rst index 93521416..6e0fc62b 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 51d6e164..c6dbb47b 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -303,7 +303,8 @@ class Config: @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 8f72d8d1..fc30b985 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -37,6 +37,7 @@ import utils.build_appimage 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 @@ if __name__ == "__main__": ) ) 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 @@ if __name__ == "__main__": # 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 b9291c74..1849c696 100644 --- a/utils/assets.py +++ b/utils/assets.py @@ -21,7 +21,6 @@ along with this program. If not, see . from __future__ import annotations import argparse -import os import subprocess import sys import zipfile @@ -29,51 +28,7 @@ import zipfile 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: @@ -90,7 +45,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(): @@ -249,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("") @@ -350,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 00000000..c43d3f37 --- /dev/null +++ b/utils/docs.py @@ -0,0 +1,150 @@ +""" +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" / "html" + docsDir = ROOT_DIR / "docs" + locsDir = ROOT_DIR / "docs" / "source" / "locales" + locsDir.mkdir(exist_ok=True) + bldRoot.mkdir(exist_ok=True, parents=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) + (docsDir / "build" / "html").rename(outDir) + 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