diff --git a/.devcontainer/on_create_command.sh b/.devcontainer/on_create_command.sh index ddc1c92..8562b17 100755 --- a/.devcontainer/on_create_command.sh +++ b/.devcontainer/on_create_command.sh @@ -3,8 +3,10 @@ set -ex -pip install -U pip setuptools wheel setuptools_scm -pip install -r requirements-dev.txt +curl -LsSf https://astral.sh/uv/install.sh | sh +. $HOME/.cargo/env +uv tool install -U ruff +uv tool install -U tox --with tox-uv # Install Transifex CLI tool into /usr/local/bin # refer to Installation instructions https://github.com/transifex/cli#installation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9d20b6c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,168 @@ +name: Test +on: + push: + branches: + - master + paths-ignore: + - 'doc/**' + pull_request: + paths-ignore: + - 'doc/**' + release: + types: [released] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 5 + matrix: + python-version: [3.9, '3.10', '3.11', '3.12', '3.13', '3.14'] + + steps: + - name: Print github context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo $GITHUB_CONTEXT + + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: "pyproject.toml" + cache-suffix: ${{ matrix.python-version }} + + - name: Install Python + run: uv python install ${{ matrix.python-version }} + env: + UV_PYTHON_PREFERENCE: only-managed + + - name: Install Transifex CLI + run: | + curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash + mv tx /usr/local/bin/tx + + - name: Tox tests + run: uv run --only-dev tox -- -v --durations=25 + + build: + name: build distribution + if: | + ${{ github.repository_owner == 'sphinx-doc' && + ( + ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) + || ( github.event_name == 'release' ) + ) + }} + needs: + - tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: astral-sh/setup-uv@v6 + + - name: build package + run: uv build + + - name: upload artifact + uses: actions/upload-artifact@v4 + with: + name: distributions + path: dist/ + + publish-to-testpypi: + name: Upload release to TestPyPI + if: ${{ github.repository_owner == 'sphinx-doc' && github.ref == 'refs/heads/master' }} # only publish to TestPyPI on push to master + needs: + - build + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/sphinx-intl + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: distributions + path: dist/ + - name: Publish package distributions to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + repository-url: https://test.pypi.org/legacy/ + + publish-to-pypi: + name: Upload release to PyPI + if: ${{ github.repository_owner == 'sphinx-doc' && startsWith(github.ref, 'refs/tags/') }} + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/sphinx-intl + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: distributions + path: dist/ + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + + github-release: + name: Sign the Python 🐍 distribution 📦 with Sigstore and upload them to GitHub Release + if: ${{ github.repository_owner == 'sphinx-doc' && startsWith(github.ref, 'refs/tags/') }} + runs-on: ubuntu-latest + needs: + - publish-to-pypi + environment: release + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + "$GITHUB_REF_NAME" dist/** + --repo "$GITHUB_REPOSITORY" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 1cc9dbd..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Test -on: - push: - paths-ignore: - - 'doc/**' - pull_request: - paths-ignore: - - 'doc/**' - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - -jobs: - tests: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: [3.9, '3.10', '3.11', '3.12', '3.13'] - max-parallel: 1 - - steps: - - name: Print github context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo $GITHUB_CONTEXT - - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - cache: 'pip' - - - name: Install tox and test related - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - - name: Install Transifex CLI - run: | - curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash - mv tx /usr/local/bin/tx - - - name: Run tox - run: | - python -V - tox -- -v --durations=25 - - lint: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - env: [lint, mypy] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: 3.9 - - - name: Install tox and any other dependencies for test - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - - name: Run tox - run: tox -e ${{ matrix.env }} diff --git a/.gitignore b/.gitignore index c1f2627..ac2022e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +uv.lock + # Created by https://www.toptal.com/developers/gitignore/api/python # Edit at https://www.toptal.com/developers/gitignore?templates=python diff --git a/CHANGES.rst b/CHANGES.rst index 3071755..e21c90d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,36 @@ CHANGES ======= +2.3.2 (2025/08/02) +================== + +Environments +------------ + +* add python-3.14 support by @rffontenelle in https://github.com/sphinx-doc/sphinx-intl/pull/115 +* Release to PyPI with digital attestations (PEP-740) by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/125 + +Incompatibility +--------------- + +Features +-------- + +Bug Fixes +--------- + +- #116: Update doesn't respect default value of locale_dirs from `-c` passed `conf.py` by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/122 + +Documentation +------------- + +Internals +--------- + +* FIX: set `exist_ok=True` in `os.makedirs()` by @Dengda98 in https://github.com/sphinx-doc/sphinx-intl/pull/120 +* using uv for dev setup, build, publish by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/114 +* Bump astral-sh/setup-uv from 3 to 6 in the all-github-actions group by @dependabot[bot] in https://github.com/sphinx-doc/sphinx-intl/pull/124 + 2.3.1 (2024/12/01) ================== diff --git a/MANIFEST.in b/MANIFEST.in index e90b104..a9047bf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,4 @@ include *.rst -include requirements-*.txt include tox.ini recursive-include tests * prune tests/__pycache__ diff --git a/README.rst b/README.rst index 276cb2e..54d3f27 100644 --- a/README.rst +++ b/README.rst @@ -24,9 +24,9 @@ Optional: support the Transifex service for translation with Sphinx_ . :alt: License :target: https://github.com/sphinx-doc/sphinx-intl/blob/master/LICENSE -.. image:: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/test.yml/badge.svg?branch=master +.. image:: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/ci.yml/badge.svg?branch=master :alt: GitHub Actions CI status - :target: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/test.yml + :target: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/ci.yml .. image:: https://img.shields.io/github/stars/sphinx-doc/sphinx-intl.svg?style=social&label=Stars :alt: GitHub stars diff --git a/checklist.rst b/checklist.rst index 488f137..ced37e1 100644 --- a/checklist.rst +++ b/checklist.rst @@ -2,12 +2,8 @@ Procedure: -1. check GitHub Actions test results: https://github.com/sphinx-doc/sphinx-intl/actions -2. update release version/date in ``CHANGES.rst`` -3. ``python -m build``, see details: setup.cfg -4. ``twine upload dist/`` -5. check PyPI page: https://pypi.org/p/sphinx-intl -6. tagging with version name that MUST following semver. e.g.: ``git tag 1.0.1`` -7. ``git push --tags`` to push tag -8. bump version in ``sphinx_intl/__init__.py`` and ``CHANGES.rst`` then commit/push - them onto GitHub +1. update release version/date in ``CHANGES.rst`` +2. create GitHub Release with new version tag, it will create a release on PyPI. + tag MUST following semver. e.g.: ``2.3.1`` +3. check PyPI page: https://pypi.org/p/sphinx-intl +4. prepareing for the next release: bump version in ``CHANGES.rst`` then commit/push it onto GitHub diff --git a/doc/dev.rst b/doc/dev.rst index cf94e24..6b12a25 100644 --- a/doc/dev.rst +++ b/doc/dev.rst @@ -18,8 +18,8 @@ Setup development environment * Requires supported Python version * Do setup under sphinx-intl.git repository root as:: - $ pip install -U pip setuptools wheel setuptools_scm - $ pip install -r requirements-dev.txt + $ pip install -U uv + $ uv sync * Install Transifex CLI tool (refer to `Installation instructions `_):: @@ -31,7 +31,7 @@ Testing Tests with supported python version that are in: * ``tox.ini`` -* ``.github/workflow/test.yml`` +* ``.github/workflow/ci.yml`` Run test -------- diff --git a/doc/index.rst b/doc/index.rst index 109dd28..9a1cfee 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -23,9 +23,9 @@ Optional: support the Transifex service for translation with Sphinx_ . :alt: License :target: https://github.com/sphinx-doc/sphinx-intl/blob/master/LICENSE -.. image:: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/test.yml/badge.svg?branch=master +.. image:: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/ci.yml/badge.svg?branch=master :alt: GitHub Actions CI status - :target: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/test.yml + :target: https://github.com/sphinx-doc/sphinx-intl/actions/workflows/ci.yml .. image:: https://img.shields.io/github/stars/sphinx-doc/sphinx-intl.svg?style=social&label=Stars :alt: GitHub stars diff --git a/pyproject.toml b/pyproject.toml index b937b8b..e86e3da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,17 +7,16 @@ authors = [ description = "Sphinx utility that make it easy to translate and to apply translation." readme = "README.rst" requires-python = ">=3.9" -license = {file = "LICENSE"} +license = "BSD-2-Clause" +license-files = ["LICENSE"] dependencies = [ - "setuptools", - "click", - "babel", + "click>=8.0.0", + "babel>=2.9.0", "sphinx", ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", - "License :: OSI Approved :: BSD License", "Topic :: Documentation", "Topic :: Documentation :: Sphinx", "Topic :: Software Development", @@ -31,12 +30,21 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Framework :: Sphinx", ] [project.optional-dependencies] test = [ - "pytest", + "pytest>=8.3.5", +] + +[dependency-groups] +dev = [ + "pytest>=8.3.5", + "ruff>=0.11.10", + "tox-gh-actions>=3.3.0", + "tox-uv>=1.25.0", ] [project.urls] @@ -46,16 +54,21 @@ Documentation = "https://sphinx-intl.readthedocs.io" [project.scripts] sphinx-intl = "sphinx_intl.commands:main" +[build-system] +requires = ["setuptools>=64", "setuptools_scm>=8"] +build-backend = "setuptools.build_meta" + [tool.setuptools] include-package-data = true -[tool.setuptools.dynamic] -version = {attr = "sphinx_intl.__version__"} - -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" +[tool.setuptools_scm] +# https://setuptools-scm.readthedocs.io/en/latest/extending/#available-implementations_1 +# because pypi does not support local version like .devN+ +local_scheme = "no-local-version" [tool.mypy] ignore_missing_imports = true strict_optional = false + +[tool.uv.sources] +sphinx-intl = { workspace = true } diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 5b80acd..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,6 +0,0 @@ --e.[test] -build -twine -wheel -tox-uv -ruff diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 6f4d040..0000000 --- a/setup.cfg +++ /dev/null @@ -1,17 +0,0 @@ -# 1. initialize -# $ pip install -U build twine - -# 2. TEST build & release: -# $ rm -Rf build/ -# $ python -m build -# $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* - -# 3. PRODUCTION build & release: -# $ rm -Rf build/ -# $ rm setup.cfg -# $ python -m build -# $ twine upload dist/* - -[egg_info] -tag_build = dev -tag_date = true diff --git a/sphinx_intl/__init__.py b/sphinx_intl/__init__.py index 3a5935a..d4f135e 100644 --- a/sphinx_intl/__init__.py +++ b/sphinx_intl/__init__.py @@ -1 +1,7 @@ -__version__ = "2.3.1" +from importlib.metadata import version, PackageNotFoundError + +try: + __version__ = version("sphinx_intl") +except PackageNotFoundError: + # package is not installed + pass diff --git a/sphinx_intl/catalog.py b/sphinx_intl/catalog.py index b78aec2..a307a49 100644 --- a/sphinx_intl/catalog.py +++ b/sphinx_intl/catalog.py @@ -32,8 +32,7 @@ def dump_po(filename, catalog, **kwargs): :return: None """ dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname) + os.makedirs(dirname, exist_ok=True) # (compatibility) line_width was the original argument used to forward # line width hints into write_po's `width` argument; if provided, @@ -55,8 +54,8 @@ def write_mo(filename, catalog, **kwargs): :return: None """ dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname) + os.makedirs(dirname, exist_ok=True) + with open(filename, "wb") as f: mofile.write_mo(f, catalog, **kwargs) diff --git a/sphinx_intl/commands.py b/sphinx_intl/commands.py index d6f485c..725cd90 100644 --- a/sphinx_intl/commands.py +++ b/sphinx_intl/commands.py @@ -233,10 +233,9 @@ def main(ctx, config, tag): ctx.locale_dir = None if ctx.config: cfg = read_config(ctx.config, tag) - if "locale_dirs" in cfg: - ctx.locale_dir = os.path.join( - os.path.dirname(ctx.config), cfg["locale_dirs"][0] - ) + # Use explicit locale_dirs if set, otherwise use Sphinx's default ['locales'] + locale_dirs = cfg.get("locale_dirs", ["locales"]) + ctx.locale_dir = os.path.join(os.path.dirname(ctx.config), locale_dirs[0]) # for pot_dir ctx.pot_dir = None diff --git a/tests/test_locale_dirs_default.py b/tests/test_locale_dirs_default.py new file mode 100644 index 0000000..3e1e18b --- /dev/null +++ b/tests/test_locale_dirs_default.py @@ -0,0 +1,65 @@ +""" + test_locale_dirs_default + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Test locale_dirs default value handling. + + :copyright: Copyright 2025 by Takayuki SHIMIZUKAWA. + :license: BSD, see LICENSE for details. +""" +import os +from unittest import mock +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from sphinx_intl.commands import main + + +def test_locale_dirs_explicit_setting(tmp_path: Path): + """Test that explicit locale_dirs setting is respected in ctx.locale_dir.""" + # Arrange + conf_content = """locale_dirs = ['custom_locale']""" + conf_path = tmp_path / 'conf.py' + conf_path.write_text(conf_content) + pot_dir = tmp_path / '_build' / 'gettext' + pot_dir.mkdir(parents=True) + (pot_dir / 'test.pot').write_text('msgid "test"') + + # Act + with mock.patch('sphinx_intl.commands.basic.update') as mock_update: + result = CliRunner().invoke(main, ['-c', str(conf_path), 'update', '-p', str(pot_dir), '-l', 'ja']) + + # Assert + assert mock_update.called, "basic.update should have been called" + called_locale_dir = mock_update.call_args[0][0] + expected_locale_dir = str(tmp_path / 'custom_locale') + assert called_locale_dir == expected_locale_dir + + +def test_locale_dirs_default_value(tmp_path: Path): + """ + Test that default locale_dirs value ['locales'] is used when not specified. + + This also serves as a regression test for issue #116: locale_dir should be + set relative to conf.py location, not relative to the current working directory. + """ + # Arrange + # No locale_dirs setting - should use default ['locales'] + conf_content = """project = 'test'""" + conf_path = tmp_path / 'conf.py' + conf_path.write_text(conf_content) + pot_dir = tmp_path / '_build' / 'gettext' + pot_dir.mkdir(parents=True) + (pot_dir / 'test.pot').write_text('msgid "test"') + + # Act + with mock.patch('sphinx_intl.commands.basic.update') as mock_update: + result = CliRunner().invoke(main, ['-c', str(conf_path), 'update', '-p', str(pot_dir), '-l', 'ja']) + + # Assert + assert mock_update.called, "basic.update should have been called" + called_locale_dir = mock_update.call_args[0][0] + expected_locale_dir = str(tmp_path / 'locales') + assert called_locale_dir == expected_locale_dir diff --git a/tox.ini b/tox.ini index 3d76503..abebb07 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{39,310,311,312,313}, + py{39,310,311,312,313,314}, lint, mypy @@ -8,9 +8,10 @@ envlist = python = 3.9: py39 3.10: py310 - 3.11: py311 + 3.11: py311, lint, check 3.12: py312 3.13: py313 + 3.14: py314 [testenv] deps=-e.[test] @@ -41,22 +42,9 @@ commands=mypy sphinx_intl [testenv:dist] usedevelop=True -deps= - build - twine +deps=twine commands= - {envpython} -m build - twine check dist/* - -[testenv:release] -deps= - build - twine -allowlist_externals=mv -commands= - -mv setup.cfg setup.cfg_ - {envpython} -m build - -mv setup.cfg_ setup.cfg + uv build twine check dist/* [flake8]