diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0ff197d01..ba6540839 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -70,7 +70,7 @@ jobs: - name: Build docs id: build - run: ./scripts/build.sh + run: python ./scripts/manage_translations.py build - name: Upload artifact - docs if: steps.build.outcome == 'success' diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 7ff462ff2..be0f37346 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -86,7 +86,7 @@ jobs: # 3- Pull translations - name: Generate template files and Transifex config file - run: ./scripts/generate_templates.sh + run: python ./scripts/manage_translations.py generate_templates - name: Pull translations from Transifex id: pull diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index ee338368e..000000000 --- a/scripts/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -# Build translated docs to pop up errors -# -# SPDX-License-Identifier: CC0-1.0 - -set -xeu - -# Fail earlier if required variables are not set -test -n ${PYDOC_LANGUAGE+x} - -cd "$(dirname $0)/.." -mkdir -p logs - -# If version is 3.12 or older, set gettext_compact. -# This confval is not needed since 3.12. -# In 3.13, its presence messes 3.13's syntax checking (?) -opts="-D language=${PYDOC_LANGUAGE} --keep-going -w ../../logs/sphinxwarnings.txt" -minor_version=$(git -C cpython/Doc branch --show-current | sed 's|^3\.||') -if [ $minor_version -lt 12 ]; then - opts="$opts -D gettext_compact=False" -fi - -make -C cpython/Doc html SPHINXOPTS="${opts}" - -# Remove empty file -if [ ! -s logs/sphinxwarnings.txt ]; then - rm logs/sphinxwarnings.txt -fi diff --git a/scripts/generate_templates.sh b/scripts/generate_templates.sh deleted file mode 100755 index d1878f411..000000000 --- a/scripts/generate_templates.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# Generate .pot files and Transifex .tx/config file -# -# SPDX-License-Identifier: CC0-1.0 -# -# The following need to be set: -# PYDOC_TX_PROJECT (e.g. python-newest) -# PYDOC_LANGUAGE (e.g. pt_BR) -# TX_TOKEN (or have a ~/.transifexrc file) - -set -xeu - -# Fail earlier if required variables are not set (do not expose TX_TOKEN) -test -n ${PYDOC_TX_PROJECT+x} -test -n ${PYDOC_LANGUAGE+x} - -# Make sure to run all commands from CPython docs locales directory -cd $(dirname $0)/../cpython/Doc/locales - -# Generate message catalog template (.pot) files -# TODO: use `make -C .. gettext` when there are only Python >= 3.12 -opts='-E -b gettext -D gettext_compact=0 -d build/.doctrees . build/gettext' -make -C .. build ALLSPHINXOPTS="$opts" - -# Generate updated Transifex project configuration file (.tx/config) -rm -rf ./.tx/config -sphinx-intl create-txconfig -sphinx-intl update-txconfig-resources \ - --transifex-organization-name=python-doc \ - --transifex-project-name=$PYDOC_TX_PROJECT \ - --locale-dir=. \ - --pot-dir=../build/gettext - -# Patch .tx/config and store in the repository to enable running tx command -# Explanation: -# - Adds 'trans.$PYDOC_LANGUAGE' to not need to pass tx pull with '-l LANGUAGE' -# - Don't remove 'file_filter' otherwise tx pull complains -# - Replace PO file path to a local directory (easier manual use of tx pull) -mkdir -p "${PYDOC_LANGUAGE}/LC_MESSAGES/.tx/" -sed .tx/config \ - -e 's|.//LC_MESSAGES/||' \ - -e "/^file_filter/{p;s/file_filter/trans.${PYDOC_LANGUAGE}/g;}" \ - > "${PYDOC_LANGUAGE}/LC_MESSAGES/.tx/config" diff --git a/scripts/manage_translations.py b/scripts/manage_translations.py new file mode 100755 index 000000000..49d016ac9 --- /dev/null +++ b/scripts/manage_translations.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python + +# SPDX-License-Identifier: CC0-1.0 + +import argparse +import contextlib +import logging +import os +import re +import shutil +import subprocess +import sys +from pathlib import Path +from typing import Optional + +from sphinx_intl.transifex import create_txconfig, update_txconfig_resources + +ROOTDIR = Path(__file__).resolve().parent.parent +COMMANDS = ["build", 'generate_templates'] + +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + + +def configure_parser() -> argparse.ArgumentParser: + """Configure and return the argument parser.""" + parser = argparse.ArgumentParser(description="Manage translation for Python documentation") + parser.add_argument("command", choices=COMMANDS, help="The command to execute") + parser.add_argument("-l", "--language", help="Language for the translated documentation") + parser.add_argument("-v", "--python-version", help="Python version to be used") + parser.add_argument("-L", "--logs-dir", default=ROOTDIR / "logs", type=Path, help="Directory for logs (default: 'logs' in root directory") + parser.add_argument("-c", "--cpython-path", default=ROOTDIR / "cpython", type=Path, help="Path to the CPython repository (default: 'cpython' in root directory") + parser.add_argument("-p", "--po-dir", type=Path, help="Path to the language team repository containing PO files (default: CPYTHON_PATH/Doc/locales/LANGUAGE/LC_MESSAGES") + parser.add_argument('-t', '--tx-project', help="Name of the Transifex project under python-doc Transifex organization") + return parser + + +def get_value(arg_value: Optional[str], arg_name: str, env_var_name: str) -> str: + """Return a CLI argument or environment variable value.""" + value = arg_value or os.getenv(env_var_name) + if not value: + logger.error(f"'{arg_name}' not provided and the environment variable {env_var_name} is not set.") + sys.exit(1) + return value + + +def validate_cpython_path(cpython_path: Path) -> None: + if not (cpython_path / "Doc" / "conf.py").exists(): + logger.error(f"Missing conf.py in {cpython_path}. Invalid CPython directory.") + sys.exit(1) + + +def validate_po_dir(po_dir: Path) -> None: + if not po_dir.exists() or not list(po_dir.glob("*.po")): + logger.error(f"Invalid locale directory '{po_dir}'. No PO files found.") + sys.exit(1) + + +def validate_tx_config(tx_config: str) -> None: + if not re.match(r"python-(newest|\d+)", tx_config): + logger.error(f"Invalid Transifex project name: {tx_config}") + sys.exit(1) + + +# contextlib implemented chdir since Python 3.11 +@contextlib.contextmanager +def chdir(path: Path): + """Temporarily change the working directory.""" + original_dir = Path.cwd() + logger.info(path) + os.chdir(path) + try: + yield + finally: + os.chdir(original_dir) + + +def build_docs(language: str, version: str, po_dir: Path, logs_dir: Path, cpython_path: Path) -> None: + """Build the documentation using Sphinx.""" + warning_log = logs_dir / "sphinx_warnings_build_docs.txt" + sphinx_opts = ["-E", "-Dgettext_compact=0", f"-Dlanguage={language}", "--keep-going", "-w", f"{warning_log}"] + locale_dirs = cpython_path / "Doc/locales" + target_locale_dir = cpython_path / "Doc/locales" / language / "LC_MESSAGES" + + # TODO Fix symlinking when po_dir is not equal to target_locale_dir + #if not po_dir.relative_to(locale_dirs) and + # not target_locale_dir.readlink() == po_dir: + # if target_locale_dir.is_symlink(): + # target_locale_dir.unlink() # remove only if it is a symlink + # if not target_locale_dir.exists() and not target_locale_dir.is_symlink(): + # (locale_dirs / language).mkdir(parents=True, exist_ok=True) + # os.symlink(po_dir, target_locale_dir) + + try: + logger.info(f"Building documentation for {language}, Python {version}.") + subprocess.run([ + "make", "-C", str(cpython_path / "Doc"), "html", f"SPHINXOPTS={' '.join(sphinx_opts)}" + ], check=True) + + if warning_log.exists() and not warning_log.stat().st_size: + warning_log.unlink() + logger.info("Removed empty warning log file.") + + except subprocess.CalledProcessError as e: + logger.error(f"Make command failed: {e}") + sys.exit(1) + + +def generate_templates(logs_dir: Path, cpython_path: Path, tx_project: str) -> None: + """Generate translation template files (a.k.a. POT files) with Sphinx""" + warning_log = logs_dir / "sphinx_warnings_generate_templates.txt" + all_sphinx_opts = [ + "-E", "-b", "gettext", "-Dgettext_compact=0", "--keep-going", + "-w", f"{warning_log}", "-d", "build/.doctrees-gettext", ".", "build/gettext" + ] + + try: + logger.info("Generating template files for Python docs.") + subprocess.run([ + "make", "-C", str(cpython_path / "Doc"), "build", f"ALLSPHINXOPTS={' '.join(all_sphinx_opts)}" + ], check=True) + + if warning_log.exists() and not warning_log.stat().st_size: + warning_log.unlink() + logger.info("Removed empty warning log file.") + + except subprocess.CalledProcessError as e: + logger.error(f"Make command failed: {e}") + sys.exit(1) + + with chdir(cpython_path / "Doc/locales"): + logger.info("Updating Transifex's resources configuration file") + Path(".tx/config").unlink(missing_ok=True) + create_txconfig() + update_txconfig_resources( + transifex_organization_name='python-doc', + transifex_project_name=tx_project, + locale_dir=Path("."), + pot_dir=Path("../build/gettext") + ) + + +def main() -> None: + parser = configure_parser() + args = parser.parse_args() + + # Set and require variable depending on the command issued by the user + cpython_path = args.cpython_path + logs_dir = Path(get_value(str(args.logs_dir), "--logs-dir", "PYDOC_LOGS")) + + if args.command == "generate_templates": + tx_project = get_value(args.tx_project, "--tx-project", "PYDOC_TX_PROJECT") + + if args.command == "build": + language = get_value(args.language, "--language", "PYDOC_LANGUAGE") + version = get_value(args.python_version, "--python-version", "PYDOC_VERSION") + po_dir = args.po_dir.absolute() if args.po_dir else cpython_path / f"Doc/locales/{language}/LC_MESSAGES" + + if args.command in ["build", "generate_templates"]: + if not shutil.which("make"): + logger.error("'make' not found. Please install it.") + sys.exit(1) + + logs_dir.mkdir(exist_ok=True) + logger.info(f"Logs will be stored in: {logs_dir}") + + if args.command == "build": + validate_cpython_path(cpython_path) + validate_po_dir(po_dir) + build_docs(language, version, po_dir, logs_dir, cpython_path) + logger.info("Documentation build completed successfully.") + elif args.command == "generate_templates": + validate_cpython_path(cpython_path) + validate_tx_config(tx_project) + generate_templates(logs_dir, cpython_path, tx_project) + + +if __name__ == "__main__": + main() + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy