|
| 1 | +#!/usr/bin/env python |
| 2 | +""" |
| 3 | +Manage translation for Python documentation |
| 4 | +""" |
| 5 | + |
| 6 | +# SPDX-License-Identifier: CC0-1.0 |
| 7 | + |
| 8 | + |
| 9 | +import argparse |
| 10 | +import logging |
| 11 | +import os |
| 12 | +import shutil |
| 13 | +import subprocess |
| 14 | +import sys |
| 15 | +from pathlib import Path |
| 16 | +from typing import Optional |
| 17 | + |
| 18 | +ROOTDIR = Path(__file__).resolve().parent.parent |
| 19 | +COMMANDS = { |
| 20 | + "build", |
| 21 | +} |
| 22 | + |
| 23 | +# Logger configuration |
| 24 | +logging.basicConfig( |
| 25 | + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" |
| 26 | +) |
| 27 | +logger = logging.getLogger(__name__) |
| 28 | + |
| 29 | + |
| 30 | +def configure_parser() -> argparse.ArgumentParser: |
| 31 | + parser = argparse.ArgumentParser(description=__doc__) |
| 32 | + parser.add_argument("command", choices=COMMANDS, help="The command to execute") |
| 33 | + parser.add_argument( |
| 34 | + "--language", "-l", help="Language for the translated documentation" |
| 35 | + ) |
| 36 | + parser.add_argument("--python-version", "-v", help="Python version to be used") |
| 37 | + parser.add_argument( |
| 38 | + "--logs-dir", |
| 39 | + "-L", |
| 40 | + default=ROOTDIR / "logs", |
| 41 | + help="Directory for logs (default: 'logs')", |
| 42 | + ) |
| 43 | + parser.add_argument( |
| 44 | + "--cpython-path", |
| 45 | + "-c", |
| 46 | + default=ROOTDIR / "cpython", |
| 47 | + type=Path, |
| 48 | + help="Path to the CPython repository (default: 'cpython')", |
| 49 | + ) |
| 50 | + return parser |
| 51 | + |
| 52 | + |
| 53 | +def get_value(env_var_name: str, arg_value: Optional[str]) -> str: |
| 54 | + """ |
| 55 | + Return value passed via command-line interface arguments *arg_value*, |
| 56 | + and if not passed, use the environment variable *env_var_name* instead. |
| 57 | + """ |
| 58 | + |
| 59 | + value = arg_value or os.getenv(env_var_name) |
| 60 | + if not value: |
| 61 | + logger.error( |
| 62 | + f"The environment variable {env_var_name} is not defined, and no value was provided by the command line." |
| 63 | + ) |
| 64 | + sys.exit(1) |
| 65 | + return value |
| 66 | + |
| 67 | + |
| 68 | +def get_minor_version(version: str) -> int: |
| 69 | + """Return Python minor *version* assuming the schema as X.Y, e.g. 3.13""" |
| 70 | + return int(version.split(".")[1]) |
| 71 | + |
| 72 | + |
| 73 | +def build(language: str, version: str, logs_dir: Path, cpython_path: Path) -> None: |
| 74 | + minor_version = get_minor_version(version) |
| 75 | + warning_log = logs_dir / "sphinxwarnings.txt" |
| 76 | + |
| 77 | + # Sphinx options. |
| 78 | + # Append gettext_compact=False if python version is 3.11 or older, |
| 79 | + # because this confval was added to conf.py only in 3.12. |
| 80 | + opts = f"-E -D language={language} --keep-going -w {warning_log}" |
| 81 | + if minor_version < 12: |
| 82 | + opts += " -D gettext_compact=False" |
| 83 | + |
| 84 | + try: |
| 85 | + # Run the make command |
| 86 | + logger.info( |
| 87 | + f"Building documentation for language {language}, Python version {version}." |
| 88 | + ) |
| 89 | + subprocess.run( |
| 90 | + ["make", "-C", cpython_path / "Doc", "html", f"SPHINXOPTS={opts}"], |
| 91 | + check=True, |
| 92 | + ) |
| 93 | + except subprocess.CalledProcessError as e: |
| 94 | + logger.error(f"Error executing the make command: {e}") |
| 95 | + raise |
| 96 | + |
| 97 | + # Remove the warning log file if it is empty |
| 98 | + if warning_log.exists() and warning_log.stat().st_size == 0: |
| 99 | + warning_log.unlink() |
| 100 | + logger.info("The warning log file was empty and has been removed.") |
| 101 | + |
| 102 | + |
| 103 | +def main() -> None: |
| 104 | + # Configure ArgumentParser |
| 105 | + parser = configure_parser() |
| 106 | + args = parser.parse_args() |
| 107 | + |
| 108 | + # Get values from environment variables or arguments |
| 109 | + language = get_value("PYDOC_LANGUAGE", args.language) |
| 110 | + version = get_value("PYDOC_VERSION", args.python_version) |
| 111 | + logs_dir = Path(get_value("PYDOC_LOGS", str(args.logs_dir))) |
| 112 | + cpython_path = args.cpython_path |
| 113 | + |
| 114 | + # Validate contents of the CPython local checkout |
| 115 | + conf_file = cpython_path / "Doc" / "conf.py" |
| 116 | + if not conf_file.exists(): |
| 117 | + logger.error( |
| 118 | + f"Configuration file '{conf_file}' not found. Invalid CPython checkout directory." |
| 119 | + ) |
| 120 | + sys.exit(1) |
| 121 | + |
| 122 | + # Check if the command is one of those that use Sphinx |
| 123 | + if args.command in [ |
| 124 | + "build", |
| 125 | + ]: |
| 126 | + # make is required |
| 127 | + if not shutil.which("make"): |
| 128 | + logger.error("Executable 'make' not found, make sure it is installed.") |
| 129 | + sys.exit(1) |
| 130 | + |
| 131 | + # Create the logs directory if it doesn't exist |
| 132 | + logs_dir.mkdir(exist_ok=True) |
| 133 | + logger.info(f"Logs will be stored in the directory: {logs_dir}") |
| 134 | + |
| 135 | + if args.command == "build": |
| 136 | + # Build the documentation |
| 137 | + try: |
| 138 | + build(language, version, logs_dir, cpython_path) |
| 139 | + logger.info("Documentation built successfully.") |
| 140 | + except Exception as e: |
| 141 | + logger.error(f"Error building the documentation: {e}") |
| 142 | + raise |
| 143 | + |
| 144 | + |
| 145 | +if __name__ == "__main__": |
| 146 | + main() |
0 commit comments