diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..de03baf --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bookworm", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/git:1": "latest", + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/python:1": "none", + "ghcr.io/devcontainers-contrib/features/act:1": {}, + "ghcr.io/devcontainers-contrib/features/ruff:1": {} + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip3 install --user -r requirements.txt", + + "onCreateCommand": "bash .devcontainer/on_create_command.sh", + + // Configure tool-specific properties. + // "customizations": {}, + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "charliermarsh.ruff" + ], + // Set *default* container specific settings.json values on container create. + "settings": { + "ruff.path": ["/usr/local/py-utils/bin/ruff"] + } + } + }, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.devcontainer/on_create_command.sh b/.devcontainer/on_create_command.sh new file mode 100755 index 0000000..ddc1c92 --- /dev/null +++ b/.devcontainer/on_create_command.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# setup + +set -ex + +pip install -U pip setuptools wheel setuptools_scm +pip install -r requirements-dev.txt + +# Install Transifex CLI tool into /usr/local/bin +# refer to Installation instructions https://github.com/transifex/cli#installation + +(cd /usr/local/bin && curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | sudo bash) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9f4ccf3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + # Name for the group, which will be used in PR titles and branch names + all-github-actions: + # Group all updates together + patterns: + - "*" + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 0000000..2277bf7 --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,49 @@ +name: Docs +on: + push: + paths: + - 'doc/**' + - '.github/workflows/doc.yml' + pull_request: + paths: + - 'doc/**' + - '.github/workflows/doc.yml' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + doc: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup python binary + uses: actions/setup-python@v5 + with: + python-version: '3' + cache: 'pip' + cache-dependency-path: 'doc/requirements.txt' + + - name: Install dependencies + run: pip install -r doc/requirements.txt + + - name: Add sphinx-build problem matcher + uses: sphinx-doc/github-problem-matcher@v1.1 + + - name: Add sphinx-lint problem matcher + uses: rffontenelle/sphinx-lint-problem-matcher@v1.0.0 + + - name: Build docs + run: make -C doc html O='--keep-going --fail-on-warning --nitpicky' + + - name: Lint docs + if: always() + run: sphinx-lint doc/ + + - name: Check links + if: always() + run: make -C doc linkcheck O='--keep-going' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 837af39..1cc9dbd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,11 @@ name: Test -on: [push, pull_request] +on: + push: + paths-ignore: + - 'doc/**' + pull_request: + paths-ignore: + - 'doc/**' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} @@ -11,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12', '3.13'] + python-version: [3.9, '3.10', '3.11', '3.12', '3.13'] max-parallel: 1 steps: @@ -25,20 +31,12 @@ jobs: with: fetch-depth: 1 - - name: pycache - uses: actions/cache@v4 - id: pycache - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} - restore-keys: | - ${{ runner.os }}-pip- - - 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: | @@ -60,7 +58,7 @@ jobs: strategy: fail-fast: false matrix: - env: [flake8, mypy] + env: [lint, mypy] steps: - name: Checkout code @@ -71,7 +69,7 @@ jobs: - name: Setup python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.9 - name: Install tox and any other dependencies for test run: | diff --git a/AUTHORS.rst b/AUTHORS.rst index c87b1eb..6ad2a0e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,5 +10,5 @@ Original This utility derived from these projects. -* https://bitbucket.org/tk0miya/sphinx-gettext-helper -* https://bitbucket.org/shimizukawa/sphinx-transifex +* ``https://bitbucket.org/tk0miya/sphinx-gettext-helper`` +* ``https://bitbucket.org/shimizukawa/sphinx-transifex`` diff --git a/CHANGES.rst b/CHANGES.rst index 781b05d..5bc91fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,39 @@ CHANGES ======= +2.3.0 (2024/11/10) +================== + +Environments +------------ + +* Drop python-3.7 support by @kloczek in https://github.com/sphinx-doc/sphinx-intl/pull/101 +* Drop Python-3.8 support by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/111 +* Avoid unrelated CI test for doc changes, and cache using setup-python action by @rffontenelle in https://github.com/sphinx-doc/sphinx-intl/pull/106 + +Incompatibility +--------------- + +Features +-------- + +* Perform update in parallel by @rtobar in https://github.com/sphinx-doc/sphinx-intl/pull/110 + +Bug Fixes +--------- + +Documentation +------------- + +Internals +--------- + +* Modernize release procedures by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/102 +* Modernize formatter: use ruff instead of flake8 by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/103 +* add devcontainer and related dependabot config by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/104 +* Add CI test for sphinx-intl's doc by @rffontenelle in https://github.com/sphinx-doc/sphinx-intl/pull/107 +* fix for lint by @shimizukawa in https://github.com/sphinx-doc/sphinx-intl/pull/108 + 2.2.0 (2024/04/20) ================== diff --git a/checklist.rst b/checklist.rst index 465caa1..488f137 100644 --- a/checklist.rst +++ b/checklist.rst @@ -4,7 +4,7 @@ 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 setup.py release sdist bdist_wheel`` +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`` diff --git a/doc/basic.rst b/doc/basic.rst index ec0452a..e896e38 100644 --- a/doc/basic.rst +++ b/doc/basic.rst @@ -13,7 +13,7 @@ Basic Features Optional features ================== -These features depends on the `transifex-client`_ tool. +These features depends on the `Transifex Client`_ tool. Please refer Installation_ section to install it. * create ``.transifexrc`` file from environment variable, without interactive @@ -27,7 +27,7 @@ You need to use `tx` command for below features: * ``tx push -s`` : push pot (translation catalogs) to transifex. * ``tx pull -l ja`` : pull po (translated catalogs) from transifex. -.. _transifex-client: https://github.com/transifex/cli +.. _Transifex Client: https://github.com/transifex/cli Installation @@ -38,5 +38,5 @@ It is strongly recommended to use virtualenv/venv for this procedure:: $ pip install sphinx-intl If you want to use `Optional Features`_, you need install Transifex CLI tool. -Please refer to `Installation instructions `_. +Please refer to `Installation instructions `_. diff --git a/doc/dev.rst b/doc/dev.rst index bae9ee4..cf94e24 100644 --- a/doc/dev.rst +++ b/doc/dev.rst @@ -19,9 +19,9 @@ Setup development environment * Do setup under sphinx-intl.git repository root as:: $ pip install -U pip setuptools wheel setuptools_scm - $ pip install -r requirements-testing.txt + $ pip install -r requirements-dev.txt -* Install Transifex CLI tool (refer to `Installation instructions `_):: +* Install Transifex CLI tool (refer to `Installation instructions `_):: $ curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash @@ -30,7 +30,6 @@ Testing Tests with supported python version that are in: -* ``setup.py`` * ``tox.ini`` * ``.github/workflow/test.yml`` diff --git a/doc/quickstart.rst b/doc/quickstart.rst index f17cf17..a52c3cd 100644 --- a/doc/quickstart.rst +++ b/doc/quickstart.rst @@ -29,7 +29,7 @@ This section describe how to translate with Sphinx_ and `sphinx-intl` command. `locale_dirs` is required and `gettext_compact` is optional. - refs `example `__. + refs `example `__. 3. Extract document's translatable messages into pot files:: diff --git a/doc/requirements.txt b/doc/requirements.txt index 8fbe6f7..66ef164 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,5 @@ sphinx sphinx_rtd_theme>=1.3 sphinx-click +sphinx-lint +. diff --git a/pyproject.toml b/pyproject.toml index aa72909..b937b8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ ] description = "Sphinx utility that make it easy to translate and to apply translation." readme = "README.rst" -requires-python = ">=3.7" +requires-python = ">=3.9" license = {file = "LICENSE"} dependencies = [ "setuptools", @@ -26,10 +26,6 @@ classifiers = [ "Topic :: Text Processing :: General", "Topic :: Utilities", "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -41,7 +37,6 @@ classifiers = [ [project.optional-dependencies] test = [ "pytest", - "mock", ] [project.urls] @@ -52,7 +47,6 @@ Documentation = "https://sphinx-intl.readthedocs.io" sphinx-intl = "sphinx_intl.commands:main" [tool.setuptools] -zip-safe = false include-package-data = true [tool.setuptools.dynamic] @@ -61,3 +55,7 @@ version = {attr = "sphinx_intl.__version__"} [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" + +[tool.mypy] +ignore_missing_imports = true +strict_optional = false diff --git a/requirements-dev.txt b/requirements-dev.txt index 2f9fab7..5b80acd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,6 @@ -e.[test] --rrequirements-testing.txt +build twine wheel +tox-uv +ruff diff --git a/requirements-testing.txt b/requirements-testing.txt deleted file mode 100644 index 1671b20..0000000 --- a/requirements-testing.txt +++ /dev/null @@ -1,4 +0,0 @@ -tox -pytest -docutils -flake8 diff --git a/setup.cfg b/setup.cfg index 4c49585..6f4d040 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,23 +1,17 @@ -# to bulid release: -# 1. COMMENT OUT `[egg_info]` section -# 2. $ pip install build -# 3. $ python -m build +# 1. initialize +# $ pip install -U build twine -# to test upload: +# 2. TEST build & release: +# $ rm -Rf build/ +# $ python -m build # $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* -# to production upload: +# 3. PRODUCTION build & release: +# $ rm -Rf build/ +# $ rm setup.cfg +# $ python -m build # $ twine upload dist/* [egg_info] tag_build = dev tag_date = true - -[flake8] -;show-pep8=true -;show-source=true -max-line-length=95 - -[mypy] -ignore_missing_imports = True -strict_optional = False diff --git a/sphinx_intl/__init__.py b/sphinx_intl/__init__.py index 5d153f2..55e4709 100644 --- a/sphinx_intl/__init__.py +++ b/sphinx_intl/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - -__version__ = '2.2.0' +__version__ = "2.3.0" diff --git a/sphinx_intl/__main__.py b/sphinx_intl/__main__.py index 3340f44..260f8ec 100644 --- a/sphinx_intl/__main__.py +++ b/sphinx_intl/__main__.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- - -if __name__ == '__main__': +if __name__ == "__main__": from sphinx_intl.commands import main + main() diff --git a/sphinx_intl/basic.py b/sphinx_intl/basic.py index fa035e3..98335a7 100644 --- a/sphinx_intl/basic.py +++ b/sphinx_intl/basic.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- - +import dataclasses +import multiprocessing as mp import os from glob import glob +from typing import Optional import click @@ -12,17 +13,70 @@ # ================================== # utility functions + def get_lang_dirs(path): - dirs = [relpath(d, path) - for d in glob(path+'/[a-z]*') - if os.path.isdir(d) and not d.endswith('pot')] + dirs = [ + relpath(d, path) + for d in glob(path + "/[a-z]*") + if os.path.isdir(d) and not d.endswith("pot") + ] return (tuple(dirs),) # ================================== # commands -def update(locale_dir, pot_dir, languages, line_width=76, ignore_obsolete=False): + +@dataclasses.dataclass(frozen=True) +class UpdateItem: + po_file: str + pot_file: str + lang: str + line_width: int + ignore_obsolete: bool + + +@dataclasses.dataclass(frozen=True) +class UpdateResult: + po_file: str + status: str + added: Optional[int] = 0 + deleted: Optional[int] = 0 + + +def _update_single_file(update_item: UpdateItem): + cat_pot = c.load_po(update_item.pot_file) + if os.path.exists(update_item.po_file): + cat = c.load_po(update_item.po_file) + msgids = {m.id for m in cat if m.id} + c.update_with_fuzzy(cat, cat_pot) + new_msgids = {m.id for m in cat if m.id} + if msgids != new_msgids: + added = new_msgids - msgids + deleted = msgids - new_msgids + c.dump_po( + update_item.po_file, + cat, + width=update_item.line_width, + ignore_obsolete=update_item.ignore_obsolete, + ) + return UpdateResult(update_item.po_file, "update", len(added), len(deleted)) + else: + return UpdateResult(update_item.po_file, "notchanged") + else: # new po file + cat_pot.locale = update_item.lang + c.dump_po( + update_item.po_file, + cat_pot, + width=update_item.line_width, + ignore_obsolete=update_item.ignore_obsolete, + ) + return UpdateResult(update_item.po_file, "create") + + +def update( + locale_dir, pot_dir, languages, line_width=76, ignore_obsolete=False, jobs=0 +): """ Update specified language's po files from pot. @@ -31,15 +85,17 @@ def update(locale_dir, pot_dir, languages, line_width=76, ignore_obsolete=False) :param tuple languages: languages to update po files :param number line_width: maximum line width of po files :param bool ignore_obsolete: ignore obsolete entries in po files + :param number jobs: number of CPUs to use :return: {'create': 0, 'update': 0, 'notchanged': 0} :rtype: dict """ status = { - 'create': 0, - 'update': 0, - 'notchanged': 0, + "create": 0, + "update": 0, + "notchanged": 0, } + to_translate = [] for dirpath, dirnames, filenames in os.walk(pot_dir): for filename in filenames: pot_file = os.path.join(dirpath, filename) @@ -48,31 +104,23 @@ def update(locale_dir, pot_dir, languages, line_width=76, ignore_obsolete=False) continue basename = relpath(base, pot_dir) for lang in languages: - po_dir = os.path.join(locale_dir, lang, 'LC_MESSAGES') + po_dir = os.path.join(locale_dir, lang, "LC_MESSAGES") po_file = os.path.join(po_dir, basename + ".po") - cat_pot = c.load_po(pot_file) - if os.path.exists(po_file): - cat = c.load_po(po_file) - msgids = set([m.id for m in cat if m.id]) - c.update_with_fuzzy(cat, cat_pot) - new_msgids = set([m.id for m in cat if m.id]) - if msgids != new_msgids: - added = new_msgids - msgids - deleted = msgids - new_msgids - status['update'] += 1 - click.echo('Update: {0} +{1}, -{2}'.format( - po_file, len(added), len(deleted))) - c.dump_po(po_file, cat, width=line_width, - ignore_obsolete=ignore_obsolete) - else: - status['notchanged'] += 1 - click.echo('Not Changed: {0}'.format(po_file)) - else: # new po file - status['create'] += 1 - click.echo('Create: {0}'.format(po_file)) - cat_pot.locale = lang - c.dump_po(po_file, cat_pot, width=line_width, - ignore_obsolete=ignore_obsolete) + to_translate.append( + UpdateItem(po_file, pot_file, lang, line_width, ignore_obsolete) + ) + + with mp.Pool(processes=jobs or None) as pool: + for result in pool.imap_unordered(_update_single_file, to_translate): + status[result.status] += 1 + if result.status == "update": + click.echo( + f"Update: {result.po_file} +{result.added}, -{result.deleted}" + ) + elif result.status == "create": + click.echo(f"Create: {result.po_file}") + else: + click.echo(f"Not Changed: {result.po_file}") return status @@ -89,7 +137,9 @@ def build(locale_dir, output_dir, languages): for lang in languages: lang_dir = os.path.join(locale_dir, lang) for dirpath, dirnames, filenames in os.walk(lang_dir): - dirpath_output = os.path.join(output_dir, os.path.relpath(dirpath, locale_dir)) + dirpath_output = os.path.join( + output_dir, os.path.relpath(dirpath, locale_dir) + ) for filename in filenames: base, ext = os.path.splitext(filename) @@ -99,10 +149,11 @@ def build(locale_dir, output_dir, languages): mo_file = os.path.join(dirpath_output, base + ".mo") po_file = os.path.join(dirpath, filename) - if (os.path.exists(mo_file) and - os.path.getmtime(mo_file) > os.path.getmtime(po_file)): + if os.path.exists(mo_file) and os.path.getmtime( + mo_file + ) > os.path.getmtime(po_file): continue - click.echo('Build: {0}'.format(mo_file)) + click.echo(f"Build: {mo_file}") cat = c.load_po(po_file) c.write_mo(mo_file, cat) @@ -128,17 +179,17 @@ def stat(locale_dir, languages): continue cat = c.load_po(po_file) - r = result[po_file.replace('\\', '/')] = { - 'translated': len(c.translated_entries(cat)), - 'fuzzy': len(c.fuzzy_entries(cat)), - 'untranslated': len(c.untranslated_entries(cat)), + r = result[po_file.replace("\\", "/")] = { + "translated": len(c.translated_entries(cat)), + "fuzzy": len(c.fuzzy_entries(cat)), + "untranslated": len(c.untranslated_entries(cat)), } click.echo( - '{0}: {1} translated, {2} fuzzy, {3} untranslated.'.format( + "{}: {} translated, {} fuzzy, {} untranslated.".format( po_file, - r['translated'], - r['fuzzy'], - r['untranslated'], + r["translated"], + r["fuzzy"], + r["untranslated"], ) ) diff --git a/sphinx_intl/catalog.py b/sphinx_intl/catalog.py index f8642a3..b78aec2 100644 --- a/sphinx_intl/catalog.py +++ b/sphinx_intl/catalog.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - import os -import io from babel.messages import pofile, mofile @@ -14,13 +11,13 @@ def load_po(filename, **kwargs): :return: catalog object """ # pre-read to get charset - with io.open(filename, 'rb') as f: + with open(filename, "rb") as f: cat = pofile.read_po(f) - charset = cat.charset or 'utf-8' + charset = cat.charset or "utf-8" # To decode lines by babel, read po file as binary mode and specify charset for # read_po function. - with io.open(filename, 'rb') as f: # FIXME: encoding VS charset + with open(filename, "rb") as f: # FIXME: encoding VS charset return pofile.read_po(f, charset=charset, **kwargs) @@ -41,12 +38,12 @@ def dump_po(filename, catalog, **kwargs): # (compatibility) line_width was the original argument used to forward # line width hints into write_po's `width` argument; if provided, # set/override the width value - if 'line_width' in kwargs: - kwargs['width'] = kwargs['line_width'] - del kwargs['line_width'] + if "line_width" in kwargs: + kwargs["width"] = kwargs["line_width"] + del kwargs["line_width"] # Because babel automatically encode strings, file should be open as binary mode. - with io.open(filename, 'wb') as f: + with open(filename, "wb") as f: pofile.write_po(f, catalog, **kwargs) @@ -60,7 +57,7 @@ def write_mo(filename, catalog, **kwargs): dirname = os.path.dirname(filename) if not os.path.exists(dirname): os.makedirs(dirname) - with io.open(filename, 'wb') as f: + 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 d14fa33..d6f485c 100644 --- a/sphinx_intl/commands.py +++ b/sphinx_intl/commands.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- """ - sphinx-intl - ~~~~~~~~~~~ - Sphinx utility that make it easy to translate and to apply translation. +sphinx-intl +~~~~~~~~~~~ +Sphinx utility that make it easy to translate and to apply translation. - :copyright: Copyright 2019 by Takayuki SHIMIZUKAWA. - :license: BSD, see LICENSE for details. +:copyright: Copyright 2019 by Takayuki SHIMIZUKAWA. +:license: BSD, see LICENSE for details. """ + import re import os import warnings @@ -19,14 +19,14 @@ from . import transifex from .pycompat import execfile_, relpath -ENVVAR_PREFIX = 'SPHINXINTL' +ENVVAR_PREFIX = "SPHINXINTL" # ================================== # utility functions -def read_config(path, passed_tags): +def read_config(path, passed_tags): tags = Tags() passed_tags = sum(passed_tags, ()) for tag in passed_tags: @@ -51,21 +51,24 @@ def read_config(path, passed_tags): def get_lang_dirs(path): - dirs = [relpath(d, path) - for d in glob(path+'/[a-z]*') - if os.path.isdir(d) and not d.endswith('pot')] + dirs = [ + relpath(d, path) + for d in glob(path + "/[a-z]*") + if os.path.isdir(d) and not d.endswith("pot") + ] return (tuple(dirs),) # ================================== # click options + class LanguagesType(click.ParamType): - name = 'languages' - envvar_list_splitter = ',' + name = "languages" + envvar_list_splitter = "," def convert(self, value, param, ctx): - langs = value.split(',') + langs = value.split(",") return tuple(langs) @@ -73,11 +76,11 @@ def convert(self, value, param, ctx): class TagsType(click.ParamType): - name = 'tags' - envvar_list_splitter = ',' + name = "tags" + envvar_list_splitter = "," def convert(self, value, param, ctx): - tags = value.split(',') + tags = value.split(",") return tuple(tags) @@ -85,93 +88,143 @@ def convert(self, value, param, ctx): option_locale_dir = click.option( - '-d', '--locale-dir', - envvar=ENVVAR_PREFIX + '_LOCALE_DIR', + "-d", + "--locale-dir", + envvar=ENVVAR_PREFIX + "_LOCALE_DIR", type=click.Path(exists=False, file_okay=False), - default='locales', metavar='', show_default=True, - help='locale directories that allow comma separated string. This option ' - 'override locale_dir in conf.py setting if provided. Default is empty ' - 'list.') + default="locales", + metavar="", + show_default=True, + help="locale directories that allow comma separated string. This option " + "override locale_dir in conf.py setting if provided. Default is empty " + "list.", +) option_pot_dir = click.option( - '--pot-dir', '-p', - envvar=ENVVAR_PREFIX + '_POT_DIR', + "--pot-dir", + "-p", + envvar=ENVVAR_PREFIX + "_POT_DIR", type=click.Path(exists=False, file_okay=False), - metavar='', show_default=True, + metavar="", + show_default=True, help="pot files directory which is generated by sphinx. " - "Default is 'pot' directory under '--locale-dir' path.") + "Default is 'pot' directory under '--locale-dir' path.", +) option_output_dir = click.option( - '--output-dir', '-o', - envvar=ENVVAR_PREFIX + '_OUTPUT_DIR', + "--output-dir", + "-o", + envvar=ENVVAR_PREFIX + "_OUTPUT_DIR", type=click.Path(exists=False, file_okay=False), - metavar='', show_default=True, + metavar="", + show_default=True, help="mo files directory where files are written. " - "Default is to match the '--locale-dir' path.") + "Default is to match the '--locale-dir' path.", +) option_tag = click.option( - '-t', '--tag', - envvar=ENVVAR_PREFIX + '_TAG', - type=TAGS, metavar='', show_default=True, + "-t", + "--tag", + envvar=ENVVAR_PREFIX + "_TAG", + type=TAGS, + metavar="", + show_default=True, multiple=True, - help="Pass tags to conf.py, as same as passed to sphinx-build -t option.") + help="Pass tags to conf.py, as same as passed to sphinx-build -t option.", +) option_language = click.option( - '-l', '--language', - envvar=ENVVAR_PREFIX + '_LANGUAGE', - type=LANGUAGES, metavar='', show_default=True, + "-l", + "--language", + envvar=ENVVAR_PREFIX + "_LANGUAGE", + type=LANGUAGES, + metavar="", + show_default=True, multiple=True, - help="Target language to update po files. Default is ALL.") + help="Target language to update po files. Default is ALL.", +) option_line_width = click.option( - '-w', '--line-width', - envvar=ENVVAR_PREFIX + '_LINE_WIDTH', - type=int, default=76, metavar='', show_default=True, + "-w", + "--line-width", + envvar=ENVVAR_PREFIX + "_LINE_WIDTH", + type=int, + default=76, + metavar="", + show_default=True, + multiple=False, + help="The maximum line width for the po files, 0 or a negative number " + "disable line wrapping", +) + +option_jobs = click.option( + "-j", + "--jobs", + envvar=ENVVAR_PREFIX + "_JOBS", + type=int, + default=0, + metavar="", + show_default=True, multiple=False, - help='The maximum line width for the po files, 0 or a negative number ' - 'disable line wrapping') + help="The number of CPUs to use for updates. 0 means all", +) option_no_obsolete = click.option( - '--no-obsolete', - envvar=ENVVAR_PREFIX + '_NO_OBSOLETE', - is_flag=True, default=False, - help='Remove obsolete #~ messages.') + "--no-obsolete", + envvar=ENVVAR_PREFIX + "_NO_OBSOLETE", + is_flag=True, + default=False, + help="Remove obsolete #~ messages.", +) option_transifex_token = click.option( - '--transifex-token', - envvar=ENVVAR_PREFIX + '_TRANSIFEX_TOKEN', - type=str, metavar='', required=True, - help="Your transifex token. (DEPRECATED)") + "--transifex-token", + envvar=ENVVAR_PREFIX + "_TRANSIFEX_TOKEN", + type=str, + metavar="", + required=True, + help="Your transifex token. (DEPRECATED)", +) option_transifex_organization_name = click.option( - '--transifex-organization-name', - envvar=ENVVAR_PREFIX + '_TRANSIFEX_ORGANIZATION_NAME', - type=str, metavar='', required=True, - help="Your transifex organization name.") + "--transifex-organization-name", + envvar=ENVVAR_PREFIX + "_TRANSIFEX_ORGANIZATION_NAME", + type=str, + metavar="", + required=True, + help="Your transifex organization name.", +) option_transifex_project_name = click.option( - '--transifex-project-name', - envvar=ENVVAR_PREFIX + '_TRANSIFEX_PROJECT_NAME', - type=str, metavar='', required=True, - help="Your transifex project name.") + "--transifex-project-name", + envvar=ENVVAR_PREFIX + "_TRANSIFEX_PROJECT_NAME", + type=str, + metavar="", + required=True, + help="Your transifex project name.", +) # ================================== # commands + @click.group() @click.option( - '-c', '--config', + "-c", + "--config", type=click.Path(exists=True, file_okay=True, dir_okay=False), - default=None, metavar='', - help='Sphinx conf.py file to read a locale directory setting.') + default=None, + metavar="", + help="Sphinx conf.py file to read a locale directory setting.", +) @option_tag @click.pass_context def main(ctx, config, tag): # load conf.py ctx.config = config if ctx.config is None: - for c in ('conf.py', 'source/conf.py'): + for c in ("conf.py", "source/conf.py"): if os.path.exists(c): ctx.config = c break @@ -180,44 +233,46 @@ def main(ctx, config, tag): ctx.locale_dir = None if ctx.config: cfg = read_config(ctx.config, tag) - if 'locale_dirs' in cfg: + if "locale_dirs" in cfg: ctx.locale_dir = os.path.join( - os.path.dirname(ctx.config), cfg['locale_dirs'][0]) + os.path.dirname(ctx.config), cfg["locale_dirs"][0] + ) # for pot_dir ctx.pot_dir = None - for d in ('_build/gettext', 'build/gettext', - '_build/locale', 'build/locale'): + for d in ("_build/gettext", "build/gettext", "_build/locale", "build/locale"): if os.path.exists(d): ctx.pot_dir = d break # for transifex_project_name ctx.transifex_project_name = None - target = os.path.normpath('.tx/config') + target = os.path.normpath(".tx/config") if os.path.exists(target): - matched = re.search(r'\[(.*)\..*\]', open(target, 'r').read()) + matched = re.search(r"\[(.*)\..*\]", open(target).read()) if matched: ctx.transifex_project_name = matched.groups()[0] click.echo( - 'Project name loaded from .tx/config: {0}'.format( - ctx.transifex_project_name)) + "Project name loaded from .tx/config: {}".format( + ctx.transifex_project_name + ) + ) ctx.default_map = { - 'update': { - 'locale_dir': ctx.locale_dir, - 'pot_dir': ctx.pot_dir, + "update": { + "locale_dir": ctx.locale_dir, + "pot_dir": ctx.pot_dir, }, - 'build': { - 'locale_dir': ctx.locale_dir, + "build": { + "locale_dir": ctx.locale_dir, }, - 'stat': { - 'locale_dir': ctx.locale_dir, + "stat": { + "locale_dir": ctx.locale_dir, }, - 'update-txconfig-resources': { - 'locale_dir': ctx.locale_dir, - 'pot_dir': ctx.pot_dir, - 'transifex_project_name': ctx.transifex_project_name, + "update-txconfig-resources": { + "locale_dir": ctx.locale_dir, + "pot_dir": ctx.pot_dir, + "transifex_project_name": ctx.transifex_project_name, }, } @@ -228,7 +283,8 @@ def main(ctx, config, tag): @option_language @option_line_width @option_no_obsolete -def update(locale_dir, pot_dir, language, line_width, no_obsolete): +@option_jobs +def update(locale_dir, pot_dir, language, line_width, no_obsolete, jobs): """ Update specified language's po files from pot. @@ -238,23 +294,32 @@ def update(locale_dir, pot_dir, language, line_width, no_obsolete): sphinx-intl update -l de,ja """ if not pot_dir: - pot_dir = os.path.join(locale_dir, 'pot') + pot_dir = os.path.join(locale_dir, "pot") if not os.path.exists(pot_dir): - msg = ("%(pot_dir)r does not exist. Please specify pot directory with " - "-p option, or preparing your pot files in %(pot_dir)r." - % locals()) - raise click.BadParameter(msg, param_hint='pot_dir') + msg = ( + "%(pot_dir)r does not exist. Please specify pot directory with " + "-p option, or preparing your pot files in %(pot_dir)r." % locals() + ) + raise click.BadParameter(msg, param_hint="pot_dir") if not language: language = get_lang_dirs(locale_dir) languages = sum(language, ()) # flatten if not languages: - msg = ("No languages are found. Please specify language with -l " - "option, or preparing language directories under %(locale_dir)r " - "directory." - % locals()) - raise click.BadParameter(msg, param_hint='language') - - basic.update(locale_dir, pot_dir, languages, line_width, ignore_obsolete=no_obsolete) + msg = ( + "No languages are found. Please specify language with -l " + "option, or preparing language directories under %(locale_dir)r " + "directory." % locals() + ) + raise click.BadParameter(msg, param_hint="language") + + basic.update( + locale_dir, + pot_dir, + languages, + line_width, + ignore_obsolete=no_obsolete, + jobs=jobs, + ) @main.command() @@ -270,8 +335,8 @@ def build(locale_dir, output_dir, language): languages = sum(language, ()) # flatten if not output_dir or ( - os.path.exists(output_dir) and - os.path.samefile(locale_dir, output_dir)): + os.path.exists(output_dir) and os.path.samefile(locale_dir, output_dir) + ): output_dir = locale_dir basic.build(locale_dir, output_dir, languages) @@ -290,7 +355,7 @@ def stat(locale_dir, language): basic.stat(locale_dir, languages) -@main.command('create-transifexrc') +@main.command("create-transifexrc") @option_transifex_token def create_transifexrc(transifex_token): """ @@ -307,7 +372,7 @@ def create_transifexrc(transifex_token): transifex.create_transifexrc(transifex_token) -@main.command('create-txconfig') +@main.command("create-txconfig") def create_txconfig(): """ Create `./.tx/config` @@ -315,22 +380,24 @@ def create_txconfig(): transifex.create_txconfig() -@main.command('update-txconfig-resources') +@main.command("update-txconfig-resources") @option_transifex_organization_name @option_transifex_project_name @option_locale_dir @option_pot_dir -def update_txconfig_resources(transifex_organization_name, transifex_project_name, - locale_dir, pot_dir): +def update_txconfig_resources( + transifex_organization_name, transifex_project_name, locale_dir, pot_dir +): """ Update resource sections of `./.tx/config`. """ if not pot_dir: - pot_dir = os.path.join(locale_dir, 'pot') + pot_dir = os.path.join(locale_dir, "pot") - transifex.update_txconfig_resources(transifex_organization_name, transifex_project_name, - locale_dir, pot_dir) + transifex.update_txconfig_resources( + transifex_organization_name, transifex_project_name, locale_dir, pot_dir + ) -if __name__ == '__main__': +if __name__ == "__main__": main(auto_envvar_prefix=ENVVAR_PREFIX) diff --git a/sphinx_intl/pycompat.py b/sphinx_intl/pycompat.py index 1505f79..257908c 100644 --- a/sphinx_intl/pycompat.py +++ b/sphinx_intl/pycompat.py @@ -1,6 +1,7 @@ """ Python compatibility functions. """ + import sys import os import warnings @@ -24,11 +25,12 @@ def relpath(path: str, start: str = os.curdir) -> str: def convert_with_2to3(filepath: str) -> str: from lib2to3.refactor import RefactoringTool, get_fixers_from_package from lib2to3.pgen2.parse import ParseError - fixers = get_fixers_from_package('lib2to3.fixes') + + fixers = get_fixers_from_package("lib2to3.fixes") refactoring_tool = RefactoringTool(fixers) source = refactoring_tool._read_python_source(filepath)[0] try: - tree = refactoring_tool.refactor_string(source, 'conf.py') + tree = refactoring_tool.refactor_string(source, "conf.py") except ParseError as err: # do not propagate lib2to3 exceptions lineno, offset = err.context[1] @@ -38,20 +40,22 @@ def convert_with_2to3(filepath: str) -> str: def execfile_(filepath: str, _globals: Any, open: Callable = open) -> None: - with open(filepath, 'rb') as f: + with open(filepath, "rb") as f: source = f.read() # compile to a code object, handle syntax errors filepath_enc = filepath.encode(fs_encoding) try: - code = compile(source, filepath_enc, 'exec') + code = compile(source, filepath_enc, "exec") except SyntaxError: # maybe the file uses 2.x syntax; try to refactor to # 3.x syntax using 2to3 source = convert_with_2to3(filepath) - code = compile(source, filepath_enc, 'exec') - warnings.warn('Support for evaluating Python 2 syntax is deprecated ' - 'and will be removed in sphinx-intl 4.0. ' - 'Convert %s to Python 3 syntax.', - source=filepath) + code = compile(source, filepath_enc, "exec") + warnings.warn( + "Support for evaluating Python 2 syntax is deprecated " + "and will be removed in sphinx-intl 4.0. " + "Convert %s to Python 3 syntax.", + source=filepath, + ) exec(code, _globals) diff --git a/sphinx_intl/sphinx_util.py b/sphinx_intl/sphinx_util.py index 2a8bb95..6f55cfc 100644 --- a/sphinx_intl/sphinx_util.py +++ b/sphinx_intl/sphinx_util.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- -from typing import Iterator, List +from collections.abc import Iterator # port from https://github.com/sphinx-doc/sphinx/blob/ad41e0b/sphinx/util/tags.py -class Tags(object): - def __init__(self, tags: List[str] = None) -> None: +class Tags: + def __init__(self, tags: list[str] = None) -> None: self.tags = dict.fromkeys(tags or [], True) def has(self, tag: str) -> bool: diff --git a/sphinx_intl/transifex.py b/sphinx_intl/transifex.py index 820cf4b..ea28ccd 100644 --- a/sphinx_intl/transifex.py +++ b/sphinx_intl/transifex.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import re import subprocess @@ -23,8 +21,8 @@ # resource names are reserved slugs, Transifex will reply with an error on these # resource names. IGNORED_RESOURCE_NAMES = ( - 'glossary', - 'settings', + "glossary", + "settings", ) TRANSIFEXRC_TEMPLATE = """\ @@ -42,22 +40,23 @@ # ================================== # utility functions + def normalize_resource_name(name): # replace path separator with '--' - name = re.sub(r'[\\/]', '--', name) + name = re.sub(r"[\\/]", "--", name) # replace unusable characters (not: -, _ ascii, digit) with '_' - name = re.sub(r'[^\-\w]', '_', name) + name = re.sub(r"[^\-\w]", "_", name) # append `_` for ignored resource names while name in IGNORED_RESOURCE_NAMES: - name += '_' + name += "_" return name def check_transifex_cli_installed(): - tx_cli_url = 'https://raw.githubusercontent.com/transifex/cli/master/install.sh' + tx_cli_url = "https://raw.githubusercontent.com/transifex/cli/master/install.sh" if not which("tx"): msg = textwrap.dedent(f"""\ Could not run "tx". @@ -101,14 +100,15 @@ def check_transifex_cli_installed(): # ================================== # commands + def create_transifexrc(transifex_token): """ Create `$HOME/.transifexrc` """ - target = os.path.normpath(os.path.expanduser('~/.transifexrc')) + target = os.path.normpath(os.path.expanduser("~/.transifexrc")) if os.path.exists(target): - click.echo('{0} already exists, skipped.'.format(target)) + click.echo(f"{target} already exists, skipped.") return if not transifex_token: @@ -116,56 +116,63 @@ def create_transifexrc(transifex_token): You need a transifex token by command option or environment. command option: --transifex-token """) - raise click.BadParameter(msg, param_hint='transifex_token') + raise click.BadParameter(msg, param_hint="transifex_token") - with open(target, 'wt') as rc: + with open(target, "w") as rc: rc.write(TRANSIFEXRC_TEMPLATE % locals()) - click.echo('Create: {0}'.format(target)) + click.echo(f"Create: {target}") def create_txconfig(): """ Create `./.tx/config` """ - target = os.path.normpath('.tx/config') + target = os.path.normpath(".tx/config") if os.path.exists(target): - click.echo('{0} already exists, skipped.'.format(target)) + click.echo(f"{target} already exists, skipped.") return - if not os.path.exists('.tx'): - os.mkdir('.tx') + if not os.path.exists(".tx"): + os.mkdir(".tx") - with open(target, 'wt') as f: + with open(target, "w") as f: f.write(TXCONFIG_TEMPLATE) - click.echo('Create: {0}'.format(target)) + click.echo(f"Create: {target}") -def update_txconfig_resources(transifex_organization_name, transifex_project_name, - locale_dir, pot_dir): +def update_txconfig_resources( + transifex_organization_name, transifex_project_name, locale_dir, pot_dir +): """ Update resource sections of `./.tx/config`. """ check_transifex_cli_installed() cmd_tmpl = ( - 'tx', - 'add', - '--organization', '%(transifex_organization_name)s', - '--project', '%(transifex_project_name)s', - '--resource', '%(resource_slug)s', - '--resource-name', '%(resource_name)s', - '--file-filter', '%(locale_dir)s//LC_MESSAGES/%(resource_path)s.po', - '--type', 'PO', - '%(pot_dir)s/%(resource_path)s.pot', + "tx", + "add", + "--organization", + "%(transifex_organization_name)s", + "--project", + "%(transifex_project_name)s", + "--resource", + "%(resource_slug)s", + "--resource-name", + "%(resource_name)s", + "--file-filter", + "%(locale_dir)s//LC_MESSAGES/%(resource_path)s.po", + "--type", + "PO", + "%(pot_dir)s/%(resource_path)s.pot", ) # convert transifex_project_name to internal name - transifex_project_name = transifex_project_name.replace(' ', '-') - transifex_project_name = re.sub(r'[^\-_\w]', '', transifex_project_name) + transifex_project_name = transifex_project_name.replace(" ", "-") + transifex_project_name = re.sub(r"[^\-_\w]", "", transifex_project_name) pot_dir = Path(pot_dir) - pot_paths = sorted(pot_dir.glob('**/*.pot')) + pot_paths = sorted(pot_dir.glob("**/*.pot")) with click.progressbar( pot_paths, length=len(pot_paths), @@ -174,7 +181,7 @@ def update_txconfig_resources(transifex_organization_name, transifex_project_nam item_show_func=lambda p: str(p), ) as progress_bar: for pot_path in progress_bar: - resource_path = str(pot_path.relative_to(pot_dir).with_suffix('')) + resource_path = str(pot_path.relative_to(pot_dir).with_suffix("")) resource_slug = resource_name = normalize_resource_name(resource_path) pot = load_po(str(pot_path)) if len(pot): @@ -182,4 +189,4 @@ def update_txconfig_resources(transifex_organization_name, transifex_project_nam cmd = [arg % lv for arg in cmd_tmpl] subprocess.check_output(cmd, shell=False) else: - click.echo('{0} is empty, skipped'.format(pot_path)) + click.echo(f"{pot_path} is empty, skipped") diff --git a/tests/conftest.py b/tests/conftest.py index 943a91f..4b2694c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ conftest ~~~~~~~~ diff --git a/tests/path.py b/tests/path.py index 480eb08..ca78554 100755 --- a/tests/path.py +++ b/tests/path.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ path ~~~~ @@ -183,4 +182,4 @@ def joinpath(self, *args): __div__ = __truediv__ = joinpath def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, str.__repr__(self)) + return '{}({})'.format(self.__class__.__name__, str.__repr__(self)) diff --git a/tests/test_basic.py b/tests/test_basic.py index a411701..274676b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ test_basic ~~~~~~~~~~ @@ -8,7 +7,7 @@ :copyright: Copyright 2019 by Takayuki SHIMIZUKAWA. :license: BSD, see LICENSE for details. """ -import mock +from unittest import mock from sphinx_intl import basic @@ -26,7 +25,7 @@ def test_update_difference_detect(temp): r2 = basic.update('locale', '_build/locale', ('ja',)) assert r2 == {'create': 0, 'update': 1, 'notchanged': 0} - with open('_build/locale/README.pot', 'r') as f: + with open('_build/locale/README.pot') as f: d = f.read() d = d.replace('test1', 'test2') with open('_build/locale/README.pot', 'w') as f: diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 6de2ca5..fb3288f 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ test_catalog ~~~~~~~~~~~~ @@ -15,7 +14,7 @@ def test_write_and_read_po_file_with_non_ascii_string(temp): from sphinx_intl import catalog cat = Catalog(locale='ja', domain='domain', fuzzy=False) - msg = Message('Hello World', u'こんにちは世界') + msg = Message('Hello World', 'こんにちは世界') cat[msg.id] = msg po_file = (temp / 'domain.po') @@ -30,7 +29,7 @@ def test_fuzzy_flag_on_catalog_update(): cat = Catalog(locale='ja', domain='domain', fuzzy=False) msg = Message('Hello Internationalized Sphinx World !', - u'こんにちは国際化されたSphinxの世界!') + 'こんにちは国際化されたSphinxの世界!') cat[msg.id] = msg assert not msg.fuzzy diff --git a/tests/test_cmd_pot.py b/tests/test_cmd_pot.py index 2ef062d..02dc4d7 100644 --- a/tests/test_cmd_pot.py +++ b/tests/test_cmd_pot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ test_cmd_pot ~~~~~~~~~~~~~ @@ -51,7 +50,7 @@ def test_update_difference_detect(temp): assert r2.output.count('Update:') == 1 assert r2.output.count('Not Changed:') == 0 - with open('_build/locale/README.pot', 'r') as f: + with open('_build/locale/README.pot') as f: d = f.read() d = d.replace('test1', 'test2') with open('_build/locale/README.pot', 'w') as f: @@ -71,7 +70,7 @@ def test_update_difference_detect(temp): def test_update_line_width(temp): - with open('_build/locale/README.pot', 'r') as f: + with open('_build/locale/README.pot') as f: template = f.read() with open('_build/locale/README.pot', 'w') as f: @@ -84,7 +83,7 @@ def test_update_line_width(temp): r1 = runner.invoke(commands.update, ['-d', 'locale', '-p', '_build/locale', '-l', 'ja']) assert r1.exit_code == 0 - with open(po_file, 'r') as f: + with open(po_file) as f: contents = f.read() assert '"foorbar identifier1"\n' in contents @@ -96,14 +95,14 @@ def test_update_line_width(temp): r2 = runner.invoke(commands.update, ['-d', 'locale', '-p', '_build/locale', '-w', '1']) assert r2.exit_code == 0 - with open(po_file, 'r') as f: + with open(po_file) as f: contents = f.read() assert '"foorbar"\n' in contents assert '"identifier2"\n' in contents def test_update_no_obsolete(temp): - with open('_build/locale/README.pot', 'r') as f: + with open('_build/locale/README.pot') as f: template = f.read() with open('_build/locale/README.pot', 'w') as f: @@ -117,7 +116,7 @@ def test_update_no_obsolete(temp): r1 = runner.invoke(commands.update, ['-d', 'locale', '-p', '_build/locale', '-l', 'ja']) assert r1.exit_code == 0 - with open(po_file, 'r') as f: + with open(po_file) as f: contents = f.read() assert '\nmsgid "foorbar1"\n' in contents assert '\nmsgid "foorbar2"\n' in contents @@ -130,7 +129,7 @@ def test_update_no_obsolete(temp): r2 = runner.invoke(commands.update, ['-d', 'locale', '-p', '_build/locale']) assert r2.exit_code == 0 - with open(po_file, 'r') as f: + with open(po_file) as f: contents = f.read() assert '\n#~ msgid "foorbar2"\n' in contents @@ -141,7 +140,7 @@ def test_update_no_obsolete(temp): r3 = runner.invoke(commands.update, ['-d', 'locale', '-p', '_build/locale', '--no-obsolete']) assert r3.exit_code == 0 - with open(po_file, 'r') as f: + with open(po_file) as f: contents = f.read() assert 'msgid "foorbar1"' not in contents assert 'msgid "foorbar2"' not in contents diff --git a/tests/test_cmd_transifex.py b/tests/test_cmd_transifex.py index 9b4e174..14520d3 100644 --- a/tests/test_cmd_transifex.py +++ b/tests/test_cmd_transifex.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ test_cmd_transifex ~~~~~~~~~~~~~~~~~~ diff --git a/tests/test_transifex.py b/tests/test_transifex.py index 7ae93f3..a8f9a18 100644 --- a/tests/test_transifex.py +++ b/tests/test_transifex.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ test_transifex ~~~~~~~~~~~~~~ diff --git a/tox.ini b/tox.ini index 09df126..3d76503 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,11 @@ [tox] envlist = - py{37,38,39,310,311,312,313}, - flake8, + py{39,310,311,312,313}, + lint, mypy [gh-actions] python = - 3.7: py37 - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 @@ -15,26 +13,53 @@ python = 3.13: py313 [testenv] -deps=-e.[transifex,test] +deps=-e.[test] setenv = BUILD_TEST_PATH = {envdir}/tests HOME={envdir} commands= py.test {posargs} -[testenv:flake8] +[testenv:lint] usedevelop=True -deps=flake8 -commands=flake8 sphinx_intl +deps=ruff +commands= + ruff check sphinx_intl + ruff format --check sphinx_intl + +[testenv:format] +usedevelop=True +deps=ruff +commands= + ruff check --fix sphinx_intl + ruff format sphinx_intl [testenv:mypy] usedevelop=True deps=mypy commands=mypy sphinx_intl -[testenv:sdist] +[testenv:dist] usedevelop=True deps= - docutils - wheel -commands={envpython} setup.py -q check -r -s sdist bdist_wheel + build + 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 + twine check dist/* + +[flake8] +# show-pep8=true +# show-source=true +max-line-length=95