From 1be16d46e55a228177d168488177a0763f1e2c76 Mon Sep 17 00:00:00 2001 From: ars Date: Thu, 25 Jun 2026 03:05:46 +0300 Subject: [PATCH] support doit for i18n and Sphinx docs --- .github/workflows/ci.yml | 4 +-- README.md | 17 ++++++++--- dodo.py | 63 ++++++++++++++++++++++++++++++++++++++-- tests/test_dodo.py | 27 +++++++++++++++++ 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4c0b65..a39ece1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,8 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.11" - - run: pip install -e ".[docs]" - - run: sphinx-build -b html -W --keep-going docs docs/_build/html + - run: pip install -e ".[dev,docs]" + - run: doit docs build: name: Build (${{ matrix.os }}) diff --git a/README.md b/README.md index e445a4b..540bd12 100644 --- a/README.md +++ b/README.md @@ -116,12 +116,21 @@ Use `doit` for repeatable checks and package builds: ```bash pip install -e .[dev] doit list -doit wheel # build dist/nexus_sync-0.1.0-py3-none-any.whl -doit package # build wheel and sdist -doit # run tests, typecheck, format check, and wheel build +doit wheel # build dist/nexus_sync-0.1.0-py3-none-any.whl +doit package # build wheel and sdist +doit i18n_compile # compile nexus-cli translation catalogs +doit pyinstaller # build standalone binaries (nexus-cli bundles catalogs) +doit # run tests, typecheck, format check, and wheel build ``` -PyInstaller standalone binaries are still available through Makefile targets: +Build the documentation (Sphinx) with the `docs` extra: + +```bash +pip install -e .[docs] +doit docs # output in docs/_build/html +``` + +PyInstaller standalone binaries are also available through Makefile targets: ``` make [client|server|cli] diff --git a/dodo.py b/dodo.py index 78d4e40..81086ad 100644 --- a/dodo.py +++ b/dodo.py @@ -1,3 +1,4 @@ +import os import sys from pathlib import Path @@ -6,6 +7,8 @@ DOIT_CONFIG = { } PYTHON = sys.executable +PYBABEL = f"{PYTHON} -m babel.messages.frontend" +SPHINX = f"{PYTHON} -m sphinx" PACKAGE_VERSION = "0.1.0" WHEEL_TARGET = f"dist/nexus_sync-{PACKAGE_VERSION}-py3-none-any.whl" SRC_FILES = list(Path("src").rglob("*.py")) @@ -13,6 +16,18 @@ TEST_FILES = list(Path("tests").rglob("*.py")) DOC_FILES = [Path("README.md"), *Path("docs").glob("*.md")] BUILD_DEPS = [Path("pyproject.toml"), Path("README.md"), *SRC_FILES] +# Localization (nexus-cli catalogs only; daemon/server logs stay in English). +LOCALE_DIR = Path("src/nexus_sync/locale") +POT_FILE = LOCALE_DIR / "nexus.pot" +PO_FILES = sorted(LOCALE_DIR.rglob("*.po")) +MO_FILES = [po.with_suffix(".mo") for po in PO_FILES] +# PyInstaller --add-data separator: ';' on Windows, ':' elsewhere. +LOCALE_DATA = f"{LOCALE_DIR}{';' if os.name == 'nt' else ':'}nexus_sync/locale" + +# Documentation (Sphinx). +DOCS_DIR = Path("docs") +DOCS_HTML = DOCS_DIR / "_build" / "html" + def task_test(): return { @@ -38,9 +53,51 @@ def task_format_check(): } +def task_i18n_extract(): + """Rebuild the .pot template from strings wrapped in _()/gettext()/ngettext().""" + return { + "actions": [f"{PYBABEL} extract -F babel.cfg -k _ -o {POT_FILE} src"], + "file_dep": [str(path) for path in SRC_FILES] + ["babel.cfg"], + "targets": [str(POT_FILE)], + "verbosity": 2, + } + + +def task_i18n_update(): + """Merge new/changed strings from the template into existing catalogs.""" + return { + "actions": [f"{PYBABEL} update -i {POT_FILE} -d {LOCALE_DIR} -D nexus"], + "file_dep": [str(POT_FILE)], + "verbosity": 2, + } + + +def task_i18n_compile(): + """Compile .po catalogs into the .mo files bundled with nexus-cli.""" + return { + "actions": [f"{PYBABEL} compile -d {LOCALE_DIR} -D nexus"], + "file_dep": [str(path) for path in PO_FILES], + "targets": [str(path) for path in MO_FILES], + "clean": True, + "verbosity": 2, + } + + +def task_docs(): + """Build the Sphinx HTML documentation (warnings are errors).""" + return { + "actions": [f"{SPHINX} -b html -W --keep-going {DOCS_DIR} {DOCS_HTML}"], + "file_dep": [str(path) for path in [*DOC_FILES, DOCS_DIR / "conf.py", *SRC_FILES]], + "targets": [str(DOCS_HTML / "index.html")], + "clean": ["rm -rf docs/_build"], + "verbosity": 2, + } + + def task_wheel(): return { "actions": [f"{PYTHON} -m build --wheel"], + "task_dep": ["i18n_compile"], "file_dep": [str(path) for path in BUILD_DEPS], "targets": [WHEEL_TARGET], "clean": True, @@ -69,9 +126,11 @@ def task_package(): def task_pyinstaller_cli(): return { "actions": [ - f"{PYTHON} -m PyInstaller --onefile src/nexus_sync/cli/__main__.py --name nexus-cli" + f"{PYTHON} -m PyInstaller --onefile src/nexus_sync/cli/__main__.py " + f'--name nexus-cli --add-data "{LOCALE_DATA}"' ], - "file_dep": [str(path) for path in BUILD_DEPS], + "task_dep": ["i18n_compile"], + "file_dep": [str(path) for path in [*BUILD_DEPS, *MO_FILES]], "clean": True, "verbosity": 2, } diff --git a/tests/test_dodo.py b/tests/test_dodo.py index 70ced83..2d215e3 100644 --- a/tests/test_dodo.py +++ b/tests/test_dodo.py @@ -43,3 +43,30 @@ def test_doit_default_tasks_include_checks_and_wheel() -> None: dodo = _load_dodo() assert dodo.DOIT_CONFIG["default_tasks"] == ["test", "typecheck", "format_check", "wheel"] + + +def test_doit_has_i18n_compile_task() -> None: + dodo = _load_dodo() + + task = dodo.task_i18n_compile() + + assert any("compile" in action for action in _actions(task)) + assert any(target.endswith("nexus.mo") for target in task["targets"]) + + +def test_doit_has_docs_task() -> None: + dodo = _load_dodo() + + task = dodo.task_docs() + + assert any("sphinx" in action and "-W" in action for action in _actions(task)) + assert "docs/conf.py" in task["file_dep"] + + +def test_pyinstaller_cli_bundles_locale_catalogs() -> None: + dodo = _load_dodo() + + task = dodo.task_pyinstaller_cli() + + assert "i18n_compile" in task["task_dep"] + assert any("--add-data" in action and "locale" in action for action in _actions(task))