Compare commits
105 Commits
Author | SHA1 | Date | |
---|---|---|---|
5f3e154a36 | |||
74a7c581c3 | |||
429ef81277 | |||
46e6eec6c5 | |||
3fd8697927 | |||
a9082a759f | |||
97bde75feb | |||
76b77e502b | |||
a628ad26b9 | |||
cb7dd30057 | |||
d39bb55588 | |||
259d02e8b2 | |||
a4a3b5e4ad | |||
e6bd2be862 | |||
10640d7f62 | |||
b1d9ac385e | |||
8cbcc30f1e | |||
2b7a621696 | |||
8e9b46f4a9 | |||
b88364aa9e | |||
f2613f1a7b | |||
814df8c700 | |||
27e96ecc85 | |||
9211708771 | |||
93502bdbc7 | |||
3ebc35d96a | |||
87e1bb3fe7 | |||
bf703e33f8 | |||
ea243cc9a3 | |||
b0bc19af25 | |||
![]() |
532957ae03 | ||
![]() |
497bd0a1ec | ||
c8acf3101c | |||
c5c9e1e0e3 | |||
dca2b9188e | |||
24e06451ed | |||
c04e675166 | |||
de046c601d | |||
5949619621 | |||
3410a0a5e0 | |||
9f886ed10b | |||
![]() |
10d5773dc9 | ||
d5fc1bfa34 | |||
e98ed008a6 | |||
2cda64a5d4 | |||
3c95882ce8 | |||
89cb7c9f35 | |||
154933c783 | |||
9b374d2718 | |||
e2955ccb5c | |||
2c11208321 | |||
38cf9c4c2b | |||
695a0b59b6 | |||
3f45cd9c51 | |||
ead58d2b5b | |||
2ec343a32e | |||
0a0af9fb52 | |||
4bb8fd7819 | |||
db5ce5e226 | |||
![]() |
cdb3069e2b | ||
8d1e18fdee | |||
![]() |
5563373256 | ||
33eb488a4b | |||
ed95fe288a | |||
da55cc9729 | |||
8d26f52f35 | |||
bdee146b63 | |||
6da1a8f74d | |||
114068b0b1 | |||
3eee104422 | |||
f1fda75ebc | |||
![]() |
8c1dad8356 | ||
19d3234840 | |||
![]() |
c4da0e405d | ||
c6145d3cec | |||
![]() |
08651e7a19 | ||
bf2b65ce3e | |||
![]() |
652517d06f | ||
ba420b8da0 | |||
4e0e2464a6 | |||
![]() |
b81bbf6969 | ||
![]() |
3f62583d2d | ||
c34a81a6d7 | |||
![]() |
4c24c7a38e | ||
8ff690e066 | |||
69969ed415 | |||
1361bddfbb | |||
07ef2c1c09 | |||
adce7af2aa | |||
9ba93c8d65 | |||
dba235f406 | |||
e307ed5945 | |||
ea5f7a3f60 | |||
83c291021f | |||
a6d4efc406 | |||
0da25ca8ad | |||
d3d6190d49 | |||
b38f71db4d | |||
0a1adac231 | |||
9604c93215 | |||
7505d29d0f | |||
0fc4d122c3 | |||
a7d6f2cb9c | |||
c758d3bb5c | |||
c32360560d |
10
.codeclimate.yml
Normal file
10
.codeclimate.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
plugins:
|
||||
bandit:
|
||||
enabled: true
|
||||
radon:
|
||||
enabled: true
|
||||
sonar-python:
|
||||
enabled: true
|
||||
config:
|
||||
tests_patterns:
|
||||
- tests/**
|
35
.github/workflows/tests.yml
vendored
Normal file
35
.github/workflows/tests.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: 'Tests (pytest)'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.7', '3.8', '3.9']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: x64
|
||||
|
||||
- name: Install poetry
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
|
||||
source $HOME/.poetry/env
|
||||
poetry --version
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
source $HOME/.poetry/env
|
||||
poetry run python --version
|
||||
poetry install -v
|
||||
poetry run pytest -s -vv --cov=padpo
|
||||
|
30
.travis.yml
Normal file
30
.travis.yml
Normal file
|
@ -0,0 +1,30 @@
|
|||
env:
|
||||
global:
|
||||
- CC_TEST_REPORTER_ID=4f307949bf2b8fece540e528b27c5304ef1df0dfbfbd0e2e7e85892e4d5a9190
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.8
|
||||
dist: bionic
|
||||
sudo: true
|
||||
- python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
|
||||
before_install:
|
||||
- pip install poetry
|
||||
|
||||
install:
|
||||
- poetry install
|
||||
|
||||
before_script:
|
||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
||||
- chmod +x ./cc-test-reporter
|
||||
- ./cc-test-reporter before-build
|
||||
script:
|
||||
- tox
|
||||
after_script:
|
||||
- echo "$TRAVIS_PULL_REQUEST"
|
||||
- echo "$TRAVIS_PYTHON_VERSION"
|
||||
- find . -name ".coverage"
|
||||
- if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_PYTHON_VERSION" == "3.7" ]]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT tests/coverage.xml; fi
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2019, Vincent Poulailleau
|
||||
Copyright (c) 2019-2020, Vincent Poulailleau
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
|
85
README.md
85
README.md
|
@ -1,23 +1,34 @@
|
|||
# padpo
|
||||
|
||||
Linter for gettext files (*.po)
|
||||
[![PyPI](https://img.shields.io/pypi/v/padpo.svg)](https://pypi.python.org/pypi/padpo)
|
||||
[![PyPI](https://img.shields.io/pypi/l/padpo.svg)](https://github.com/vpoulailleau/padpo/blob/master/LICENSE)
|
||||
[![Code style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
|
||||
[![Downloads](https://pepy.tech/badge/padpo)](https://pepy.tech/project/padpo)
|
||||
[![Tests](https://github.com/AFPy/padpo/workflows/Tests/badge.svg)](https://github.com/AFPy/padpo/actions?query=workflow%3ATests)
|
||||
[![Maintainability](https://api.codeclimate.com/v1/badges/bbd3044291527d667778/maintainability)](https://codeclimate.com/github/AFPy/padpo/maintainability)
|
||||
[![Test Coverage](https://api.codeclimate.com/v1/badges/bbd3044291527d667778/test_coverage)](https://codeclimate.com/github/AFPy/padpo/test_coverage)
|
||||
|
||||
Linter for gettext files (\*.po)
|
||||
|
||||
Created to help the translation of official Python docs in French: https://github.com/python/python-docs-fr
|
||||
|
||||
Il faut demander aux traducteurs s'ils n'ont pas de pot quand ils traduisent, maintenant ils ont `padpo`…
|
||||
:smile: :laughing: :stuck_out_tongue_winking_eye: :joy: (note : il était tard le soir quand j'ai trouvé le nom).
|
||||
|
||||
**WORK IN PROGRESS**
|
||||
|
||||
## License
|
||||
|
||||
BSD 3-clause
|
||||
|
||||
Pull request are welcome.
|
||||
|
||||
## Padpo is part of poutils!
|
||||
|
||||
[Poutils](https://pypi.org/project/poutils) (`.po` utils) is a metapackage to easily install useful Python tools to use with po files
|
||||
and `padpo` is a part of it! Go check out [Poutils](https://pypi.org/project/poutils) to discover the other tools!
|
||||
|
||||
## Usage
|
||||
|
||||
Using the *activated virtual environment* created during the installation:
|
||||
Using the _activated virtual environment_ created during the installation:
|
||||
|
||||
For a local input file:
|
||||
|
||||
|
@ -45,6 +56,11 @@ padpo --github python/python-docs-fr/pull/978
|
|||
|
||||
![Screenshot](screenshot.png)
|
||||
|
||||
### Color
|
||||
|
||||
By default, the output is colorless, and formatted like GCC messages. You can use `-c`
|
||||
or `--color` option to get a colored output.
|
||||
|
||||
## Installation
|
||||
|
||||
### Automatic installation
|
||||
|
@ -55,19 +71,7 @@ pip install padpo
|
|||
|
||||
### Manual installation
|
||||
|
||||
1. Create a virtual environment with Python 3.7 and above
|
||||
|
||||
```bash
|
||||
python3.7 -m venv venv
|
||||
```
|
||||
|
||||
2. Activate the virtual environment
|
||||
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
3. Install dependencies
|
||||
1. Install dependencies
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
|
@ -75,19 +79,56 @@ pip install padpo
|
|||
|
||||
Note: this uses `poetry` that you can get here: https://poetry.eustace.io/docs/
|
||||
|
||||
2. Use virtual environment$
|
||||
|
||||
```bash
|
||||
poetry shell
|
||||
```
|
||||
|
||||
## Update on PyPI
|
||||
|
||||
`./deliver.sh`
|
||||
|
||||
## Changelog
|
||||
|
||||
### v0.11.0 (2021-02-02)
|
||||
|
||||
- update glossary (fix #58)
|
||||
|
||||
### v0.10.0 (2020-12-04)
|
||||
|
||||
- use `pygrammalecte` v1.3.0
|
||||
- use GitHub Actions
|
||||
|
||||
### v0.9.0 (2020-09-07)
|
||||
|
||||
- use `pygrammalecte` default message for spelling errors
|
||||
|
||||
### v0.8.0 (2020-08-25)
|
||||
|
||||
- use [`pygrammalecte`](https://github.com/vpoulailleau/pygrammalecte)
|
||||
- add continuous integration
|
||||
- fix #12, #13, #14, #15, #17, #18, #20
|
||||
- add `--color` CLI option to get a colored output (default is colorless)
|
||||
|
||||
### v0.7.0 (2019-12-11)
|
||||
|
||||
- add `--version` CLI option to display the current version of `padpo`
|
||||
- `--input-path` CLI option now accepts several paths as in
|
||||
`padpo --input-path file1.po file2.po directory1 directory2` or
|
||||
`padpo -i file1.po file2.po directory1 directory2`
|
||||
|
||||
### v0.6.0 (2019-12-9)
|
||||
|
||||
- check errors against defined glossaries
|
||||
|
||||
### v0.5.0 (2019-12-3)
|
||||
|
||||
* check spelling errors with grammalecte
|
||||
* tag releases!
|
||||
- check spelling errors with grammalecte
|
||||
- tag releases!
|
||||
|
||||
### v0.4.0 (2019-12-2)
|
||||
|
||||
* use poetry: https://poetry.eustace.io/docs/
|
||||
* add some tests with tox and pytests
|
||||
* fix some false positive issues with grammalecte
|
||||
- use poetry: https://poetry.eustace.io/docs/
|
||||
- add some tests with tox and pytests
|
||||
- fix some false positive issues with grammalecte
|
||||
|
|
|
@ -7,7 +7,7 @@ git rebase
|
|||
git status
|
||||
|
||||
# version management
|
||||
VERSION=`grep "version =" pyproject.toml | sed -e 's/.*= "//' -e 's/"//'`
|
||||
VERSION=`egrep "^version =" pyproject.toml | sed -e 's/.*= "//' -e 's/"//'`
|
||||
VERSION="v${VERSION}"
|
||||
echo "Version to be delivered: ${VERSION}"
|
||||
echo -n "Is it OK? (y/n) [y]: "
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
"""Checkers list."""
|
||||
from padpo.checkers.doublespace import DoubleSpaceChecker
|
||||
from padpo.checkers.empty import EmptyChecker
|
||||
from padpo.checkers.fuzzy import FuzzyChecker
|
||||
from padpo.checkers.glossary import GlossaryChecker
|
||||
from padpo.checkers.grammalecte import GrammalecteChecker
|
||||
from padpo.checkers.linelength import LineLengthChecker
|
||||
from padpo.checkers.nbsp import NonBreakableSpaceChecker
|
||||
|
||||
checkers = [
|
||||
DoubleSpaceChecker(),
|
||||
EmptyChecker(),
|
||||
FuzzyChecker(),
|
||||
GrammalecteChecker(),
|
||||
GlossaryChecker(),
|
||||
LineLengthChecker(),
|
||||
NonBreakableSpaceChecker(),
|
||||
]
|
||||
|
|
|
@ -26,6 +26,12 @@ class Checker(ABC):
|
|||
"""Check an item in a `*.po` file."""
|
||||
return NotImplementedError
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Let any checker register argparse arguments."""
|
||||
|
||||
def configure(self, args):
|
||||
"""Store the result of parse_args, to get back arguments from self.add_arguments."""
|
||||
|
||||
|
||||
def replace_quotes(match):
|
||||
"""Replace match with « xxxxxxx »."""
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
"""Checker for double spaces."""
|
||||
|
||||
import re
|
||||
|
||||
from padpo.checkers.baseclass import Checker
|
||||
from padpo.pofile import PoItem
|
||||
|
||||
|
||||
class DoubleSpaceChecker(Checker):
|
||||
"""Checker for double spaces."""
|
||||
|
||||
name = "Double space"
|
||||
|
||||
def check_item(self, item: PoItem):
|
||||
"""Check an item in a `*.po` file."""
|
||||
for match in re.finditer(
|
||||
r"(.{0,30})\s\s(.{0,30})", item.msgstr_full_content
|
||||
):
|
||||
item.add_warning(
|
||||
self.name,
|
||||
f"Double spaces detected between ###{match.group(1)}### "
|
||||
f"and ###{match.group(2)}###",
|
||||
)
|
366
padpo/checkers/glossary.py
Normal file
366
padpo/checkers/glossary.py
Normal file
|
@ -0,0 +1,366 @@
|
|||
"""Checker for glossary usage."""
|
||||
|
||||
import re
|
||||
|
||||
from padpo.checkers.baseclass import Checker
|
||||
from padpo.pofile import PoItem
|
||||
|
||||
|
||||
class GlossaryChecker(Checker):
|
||||
"""Checker for glossary usage."""
|
||||
|
||||
name = "Glossary"
|
||||
|
||||
def check_item(self, item: PoItem):
|
||||
"""Check an item in a `*.po` file."""
|
||||
if not item.msgstr_full_content:
|
||||
return # no warning
|
||||
original_content = item.msgid_rst2txt.lower()
|
||||
original_content = re.sub(r"« .*? »", "", original_content)
|
||||
translated_content = item.msgstr_full_content.lower()
|
||||
for word, translations in glossary.items():
|
||||
if re.match(fr"\b{word.lower()}\b", original_content):
|
||||
for translated_word in translations:
|
||||
if translated_word.lower() in translated_content:
|
||||
break
|
||||
else:
|
||||
possibilities = '"'
|
||||
possibilities += '", "'.join(translations[:-1])
|
||||
if len(translations) > 1:
|
||||
possibilities += '" or "'
|
||||
possibilities += translations[-1]
|
||||
possibilities += '"'
|
||||
item.add_warning(
|
||||
self.name,
|
||||
f'Found "{word}" that is not translated in '
|
||||
f"{possibilities} in ###{item.msgstr_full_content}"
|
||||
"###.",
|
||||
)
|
||||
|
||||
|
||||
# https://github.com/python/python-docs-fr/blob/
|
||||
# 662b4ec48b27daa4fbef05cddc43da0d894b29e7/CONTRIBUTING.rst
|
||||
glossary = {
|
||||
"-like": ["-compatible"],
|
||||
"abstract data type": ["type abstrait"],
|
||||
"abstract data types": ["types abstraits"],
|
||||
"argument": ["argument"],
|
||||
"arguments": ["arguments"],
|
||||
"backport": ["rétroporter"],
|
||||
"backslash": ["antislash", "*backslash*"],
|
||||
"backslashes": ["antislashs", "*backslashes*"],
|
||||
"backtrace": ["trace d'appels", "trace de pile"],
|
||||
"backtraces": ["traces d'appels", "traces de pile"],
|
||||
"big-endian": ["gros-boutiste"],
|
||||
"bound": ["lié"],
|
||||
"bug": ["bogue"],
|
||||
"bugs": ["bogues"],
|
||||
"built-in": ["native", "natif"],
|
||||
"built-ins": ["fonctions natives"],
|
||||
"bytecode": ["code intermédiaire"],
|
||||
"callback": ["fonction de rappel"],
|
||||
"callbacks": ["fonctions de rappel"],
|
||||
"call stack": ["pile d'appels"],
|
||||
"call stacks": ["piles d'appels"],
|
||||
"caught": ["interceptée", "interceptées"], # exception
|
||||
"debugging": ["débogage"],
|
||||
"deep copy": ["copie récursive", "copie profonde"],
|
||||
"double quote": ["guillemet"],
|
||||
"double quotes": ["guillemets"],
|
||||
"deprecated": ["obsolète"],
|
||||
"e.g.": ["p. ex.", "par exemple"],
|
||||
"et al.": ["et autre", "et autres", "et ailleurs"],
|
||||
"export": ["exporter", "exportation"],
|
||||
"exports": ["exportations"],
|
||||
"expression": ["expression"],
|
||||
"expressions": ["expressions"],
|
||||
"framework": ["cadriciel"],
|
||||
"frozen package": ["paquet figé"],
|
||||
"frozen packages": ["paquets figés"],
|
||||
"frozen set": ["ensemble figé"],
|
||||
"frozen sets": ["ensembles figés"],
|
||||
"garbage collector": ["ramasse-miettes"],
|
||||
"getter": ["accesseur"],
|
||||
"getters": ["accesseurs"],
|
||||
"i.e": ["c.-à-d.", "c'est-à-dire"],
|
||||
"identifier": ["identifiant"],
|
||||
"identifiers": ["identifiants"],
|
||||
"index": ["indice"], # chaînes de caractères
|
||||
"indexes": ["indices"], # chaînes de caractères
|
||||
"immutable": ["immuable"],
|
||||
"import": ["importer", "importation"],
|
||||
"imports": ["importations"],
|
||||
"installer": ["installateur"],
|
||||
"installers": ["installateurs"],
|
||||
"interpreter": ["interpréteur"],
|
||||
"interpreters": ["interpréteurs"],
|
||||
"keyword(?! argument)": ["mot clé"],
|
||||
"keywords": ["mots clés"],
|
||||
"keyword argument": ["argument nommé"],
|
||||
"keyword arguments": ["arguments nommés"],
|
||||
"library": ["bibliothèque"],
|
||||
"libraries": ["bibliothèques"],
|
||||
"list comprehension": ["liste en compréhension"],
|
||||
"list comprehensions": ["listes en compréhension"],
|
||||
"little-endian": ["petit-boutiste"],
|
||||
"mixin type": ["type de mélange"],
|
||||
"mixin types": ["types de mélange"],
|
||||
"mutable": ["muable"],
|
||||
"namespace": ["espace de nommage", "espace de noms"],
|
||||
"namespaces": ["espaces de nommage", "espaces de noms"],
|
||||
"parameter": ["paramètre"],
|
||||
"parameters": ["paramètres"],
|
||||
"pickle": ["sérialiser"],
|
||||
"prompt": ["invite"],
|
||||
"raise": ["lever"],
|
||||
"raised": ["levé"],
|
||||
"regular expression": ["expression rationnelle", "expression régulière"],
|
||||
"regular expressions": [
|
||||
"expressions rationnelles",
|
||||
"expressions régulières",
|
||||
],
|
||||
"return": ["renvoie"],
|
||||
"returns": ["renvoie"],
|
||||
"returned": ["renvoyé", "renvoyée", "renvoyés", "renvoyées"],
|
||||
"roughly": ["approximativement", "à peu près"],
|
||||
"setter": ["mutateur"],
|
||||
"setters": ["mutateurs"],
|
||||
"simple quote": ["guillemet simple"],
|
||||
"simple quotes": ["guillemets simples"],
|
||||
"socket": ["connecteur", "interface de connexion"],
|
||||
"sockets": ["connecteurs", "interfaces de connexion"],
|
||||
"specify": ["définir", "préciser"],
|
||||
"statement": ["instruction"],
|
||||
"statements": ["instructions"],
|
||||
"subprocess": ["sous-processus"],
|
||||
"subprocesses": ["sous-processus"],
|
||||
"support": [
|
||||
"prendre en charge",
|
||||
"prend en charge",
|
||||
"prennent en charge",
|
||||
"implémenter",
|
||||
"implémente",
|
||||
"implémentent",
|
||||
],
|
||||
"thread": ["fil d'exécution"],
|
||||
"threads": ["fils d'exécution"],
|
||||
"traceback": ["trace d'appels", "trace de pile"],
|
||||
"tracebacks": ["traces d'appels", "traces de pile"],
|
||||
"(?<![-])tuple": ["*n*-uplet"],
|
||||
"(?<![-])tuples": ["*n*-uplets"],
|
||||
"2-tuple": ["paire", "couple"],
|
||||
"3-tuple": ["triplet"],
|
||||
"4-tuple": ["quadruplet"],
|
||||
"5-tuple": ["quintuplet"],
|
||||
"6-tuple": ["sextuplet"],
|
||||
"7-tuple": ["heptuplet"], # …
|
||||
"typically": ["normalement", "habituellement", "comme d'habitude"],
|
||||
"underscore": ["tiret bas", "*underscore*", "sous-tiret"],
|
||||
"underscores": ["tirets bas", "*underscores*", "sous-tiret"],
|
||||
"whitespace": ["caractère d'espacement"],
|
||||
"whitespaces": ["caractères d'espacement"],
|
||||
}
|
||||
|
||||
# https://github.com/python/python-docs-fr/blob/
|
||||
# 25e6bb0dc12c0c22c1053e5c0861a163a84b9c02/glossary.po
|
||||
glossary.update(
|
||||
{
|
||||
"abstract base class": ["classe de base abstraite"],
|
||||
"abstract base classes": ["classes de base abstraites"],
|
||||
"annotation": ["annotation"],
|
||||
"annotations": ["annotations"],
|
||||
"asynchronous context manager": ["gestionnaire de contexte asynchrone"],
|
||||
"asynchronous context managers": ["gestionnaires de contexte asynchrone"],
|
||||
"asynchronous generator": ["générateur asynchrone"],
|
||||
"asynchronous generators": ["générateurs asynchrones"],
|
||||
"asynchronous iterable": ["itérable asynchrone"],
|
||||
"asynchronous iterables": ["itérables asynchrones"],
|
||||
"asynchronous": ["asynchrone"],
|
||||
"attribute": ["attribut"],
|
||||
"attributes": ["attributs"],
|
||||
"awaitable": ["*awaitable*"],
|
||||
"BDFL": ["*BDFL*"],
|
||||
"binary file": ["fichier binaire"],
|
||||
"binary files": ["fichiers binaires"],
|
||||
"bytes-like object": ["objet octet-compatible"],
|
||||
"bytes-like objects": ["objets octet-compatible"],
|
||||
"bytecode": ["code intermédiaire", "*bytecode*"],
|
||||
"class": ["classe"],
|
||||
"classes": ["classes"],
|
||||
"class variable": ["variable de classe"],
|
||||
"class variables": ["variables de classe"],
|
||||
"coercion": ["coercition"],
|
||||
"coercions": ["coercitions"],
|
||||
"complex number": ["nombre complexe"],
|
||||
"complex numbers": ["nombres complexes"],
|
||||
"context manager": ["gestionnaire de contexte"],
|
||||
"context managers": ["gestionnaires de contexte"],
|
||||
"context variable": ["variable de contexte"],
|
||||
"context variables": ["variables de contexte"],
|
||||
"contiguous": ["contigu"],
|
||||
"coroutine": ["coroutine"],
|
||||
"coroutines": ["coroutines"],
|
||||
"CPython": ["CPython"],
|
||||
"decorator": ["décorateur"],
|
||||
"decorators": ["décorateurs"],
|
||||
"descriptor": ["descripteur"],
|
||||
"descriptors": ["descripteurs"],
|
||||
"dictionary": ["dictionnaire"],
|
||||
"dictionaries": ["dictionnaires"],
|
||||
"dictionary comprehension": [
|
||||
"dictionnaire en compréhension",
|
||||
"dictionnaire en intention",
|
||||
],
|
||||
"dictionary view": ["vue de dictionnaire"],
|
||||
"dictionary views": ["vues de dictionnaire"],
|
||||
"docstring": ["*docstring*", "chaîne de documentation"],
|
||||
"docstrings": ["*docstrings*", "chaînes de documentation"],
|
||||
"duck-typing": ["*duck-typing*"],
|
||||
"extension module": ["module d'extension"],
|
||||
"extension modules": ["modules d'extension"],
|
||||
"f-string": ["f-string"],
|
||||
"f-strings": ["f-strings"],
|
||||
"file object": ["objet fichier"],
|
||||
"file objects": ["objets fichier"],
|
||||
"file-like object": ["objet fichier-compatible"],
|
||||
"file-like objects": ["objets fichier-compatible"],
|
||||
"finder": ["chercheur"],
|
||||
"finders": ["chercheurs"],
|
||||
"floor division": ["division entière"],
|
||||
"floor divisions": ["divisions entières"],
|
||||
"function": ["fonction"],
|
||||
"functions": ["fonctions"],
|
||||
"function annotation": ["annotation de fonction"],
|
||||
"function annotations": ["annotations de fonction"],
|
||||
"__future__": ["__future__"],
|
||||
"garbage collection": ["ramasse-miettes"],
|
||||
"generator": ["générateur", "génératrice"],
|
||||
"generators": ["générateurs"],
|
||||
"generator iterator": ["itérateur de générateur"],
|
||||
"generator iterators": ["iterateurs de générateur"],
|
||||
"generator expression": ["expression génératrice"],
|
||||
"generator expressions": ["expressions génératrices"],
|
||||
"generic function": ["fonction générique"],
|
||||
"generic functions": ["fonctions génériques"],
|
||||
"generic type": ["type générique"],
|
||||
"generic types": ["types génériques"],
|
||||
"GIL": ["GIL"],
|
||||
"global interpreter lock": ["verrou global de l'interpréteur"],
|
||||
"hash-based pyc": ["*pyc* utilisant le hachage"],
|
||||
"hashable": ["hachable"],
|
||||
# "IDLE": ["IDLE"], # confusion with "idle"
|
||||
"immutable": ["immuable"],
|
||||
"import path": ["chemin des importations"],
|
||||
"import paths": ["chemins des importations"],
|
||||
"importing": ["importer", "important", "importation"],
|
||||
"importer": ["importateur"],
|
||||
"importers": ["importateurs"],
|
||||
"interactive": ["interactif", "interactive"],
|
||||
"interpreted": ["interprété"],
|
||||
"interpreter shutdown": ["arrêt de l'interpréteur"],
|
||||
"iterable": ["itérable"],
|
||||
"iterables": ["itérables"],
|
||||
"iterator": ["itérateur"],
|
||||
"iterators": ["itérateurs"],
|
||||
"key function": ["fonction clé"],
|
||||
"key functions": ["fonctions clé"],
|
||||
"keyword argument": ["argument nommé"],
|
||||
"keyword arguments": ["arguments nommés"],
|
||||
"lambda": ["lambda"],
|
||||
"list": ["*list*", "liste"],
|
||||
"lists": ["listes"],
|
||||
"list comprehension": ["liste en compréhension", "liste en intention"],
|
||||
"loader": ["chargeur"],
|
||||
"loaders": ["chargeurs"],
|
||||
"magic method": ["méthode magique"],
|
||||
"magic methods": ["méthodes magiques"],
|
||||
"mapping": ["tableau de correspondance"],
|
||||
"mappings": ["tableaux de correspondance"],
|
||||
"meta path finder": ["chercheur dans les méta-chemins"],
|
||||
"meta path finders": ["chercheurs dans les méta-chemins"],
|
||||
"metaclass": ["métaclasse"],
|
||||
"metaclasses": ["métaclasses"],
|
||||
"method": ["méthode"],
|
||||
"methods": ["méthodes"],
|
||||
"method resolution order": ["ordre de résolution des méthodes"],
|
||||
"module": ["module"],
|
||||
"modules": ["modules"],
|
||||
"module spec": ["spécificateur de module"],
|
||||
"module specs": ["spécificateurs de module"],
|
||||
"MRO": ["MRO"],
|
||||
"mutable": ["muable"],
|
||||
"named tuple": ["*n*-uplet nommé"],
|
||||
"named tuples": ["*n*-uplets nommés"],
|
||||
# "tuple": already in glossary
|
||||
# "tuples": already in glossary
|
||||
"namespace": ["espace de nommage", "espace de noms"],
|
||||
"namespaces": ["espaces de nommage", "espaces de noms"],
|
||||
"namespace package": ["paquet-espace de nommage"],
|
||||
"namespace packages": ["paquets-espace de nommage"],
|
||||
"nested scope": ["portée imbriquée"],
|
||||
"nested scopes": ["portées imbriquées"],
|
||||
"new-style class": ["nouvelle classe"],
|
||||
"new-style classes": ["nouvelles classes"],
|
||||
"object": ["objet"],
|
||||
"objects": ["objets"],
|
||||
"package": ["paquet"],
|
||||
"packages": ["paquets"],
|
||||
"parameter": ["paramètre"],
|
||||
"parameters": ["paramètres"],
|
||||
"path entry": ["entrée de chemin"],
|
||||
"path entries": ["entrées de chemin"],
|
||||
"path entry finder": ["chercheur de chemins"],
|
||||
"path entry finders": ["chercheurs de chemins"],
|
||||
"path entry hook": ["point d'entrée pour la recherche dans *path*"],
|
||||
"path entry hooks": ["points d'entrée pour la recherche dans *path*"],
|
||||
"path based finder": ["chercheur basé sur les chemins"],
|
||||
"path based finders": ["chercheurs basés sur les chemins"],
|
||||
"path-like object": ["objet simili-chemin"],
|
||||
"path-like objects": ["objets simili-chemin"],
|
||||
"PEP": ["PEP"],
|
||||
"portion": ["portion"],
|
||||
"portions": ["portions"],
|
||||
"positional argument": ["argument positionnel"],
|
||||
"positional arguments": ["arguments positionnels"],
|
||||
"provisional API": ["API provisoire"],
|
||||
"provisional package": ["paquet provisoire"],
|
||||
"provisional packages": ["paquets provisoires"],
|
||||
"pythonic": ["*pythonique*", "*pythoniques*"],
|
||||
"qualified name": ["nom qualifié"],
|
||||
"qualified names": ["noms qualifiés"],
|
||||
"reference count": ["nombre de références"],
|
||||
"regular package": ["paquet classique"],
|
||||
"regular packages": ["paquets classiques"],
|
||||
"__slots__": ["``__slots__``"],
|
||||
"sequence": ["séquence"],
|
||||
"sequences": ["séquences"],
|
||||
"set comprehension": ["ensemble en compréhension", "ensemble en intention"],
|
||||
"single dispatch": ["distribution simple"],
|
||||
"slice": ["tranche"],
|
||||
"slices": ["tranches"],
|
||||
"special method": ["méthode spéciale"],
|
||||
"special methods": ["méthodes spéciales"],
|
||||
"statement": ["instruction"],
|
||||
"statements": ["instructions"],
|
||||
"text encoding": ["encodage de texte"],
|
||||
"text encodings": ["encodages de texte"],
|
||||
"text file": ["fichier texte"],
|
||||
"text files": ["fichiers texte"],
|
||||
"triple quoted string": ["chaîne entre triple guillemets"],
|
||||
"triple quoted strings": ["chaîne entre triple guillemets"],
|
||||
"type": ["type"],
|
||||
"types": ["types"],
|
||||
"type alias": ["alias de type"],
|
||||
"type aliases": ["alias de type"],
|
||||
"type hint": ["indication de type"],
|
||||
"type hints": ["indications de type"],
|
||||
"universal newlines": ["retours à la ligne universels"],
|
||||
"variable annotation": ["annotation de variable"],
|
||||
"variable annotations": ["annotations de variables"],
|
||||
"virtual environment": ["environnement virtuel"],
|
||||
"virtual environments": ["environnements virtuels"],
|
||||
"virtual machine": ["machine virtuelle"],
|
||||
"virtual machines": ["machines virtuelles"],
|
||||
"zen of Python": ["le zen de Python"],
|
||||
}
|
||||
)
|
|
@ -1,18 +1,21 @@
|
|||
"""Checker for grammar errors."""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Set
|
||||
from zipfile import ZipFile
|
||||
from typing import Set, Optional
|
||||
|
||||
import requests
|
||||
import simplelogging
|
||||
from pygrammalecte import (
|
||||
GrammalecteGrammarMessage,
|
||||
GrammalecteMessage,
|
||||
GrammalecteSpellingMessage,
|
||||
grammalecte_text,
|
||||
)
|
||||
|
||||
from padpo.checkers.baseclass import Checker, replace_quotes
|
||||
from padpo.pofile import PoItem, PoFile
|
||||
from padpo.checkers.glossary import glossary
|
||||
from padpo.pofile import PoFile, PoItem
|
||||
|
||||
log = simplelogging.get_logger()
|
||||
|
||||
|
@ -25,151 +28,100 @@ class GrammalecteChecker(Checker):
|
|||
def __init__(self):
|
||||
"""Initialiser."""
|
||||
super().__init__()
|
||||
self.dir = None
|
||||
self.personal_dict: Set[str] = set()
|
||||
self.get_personal_dict()
|
||||
|
||||
@staticmethod
|
||||
def run_grammalecte(filename: str) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
[
|
||||
"grammalecte-cli.py",
|
||||
"-f",
|
||||
filename,
|
||||
"-off",
|
||||
"apos",
|
||||
"--json",
|
||||
"--only_when_errors",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
def check_file(self, pofile: PoFile):
|
||||
"""Check a `*.po` file."""
|
||||
if not isinstance(pofile, PoFile):
|
||||
log.error("%s is not an instance of PoFile", str(pofile))
|
||||
_, filename = tempfile.mkstemp(
|
||||
suffix=".txt", prefix="padpo_", text=True
|
||||
)
|
||||
with open(filename, "w", encoding="utf8") as f:
|
||||
text = pofile.rst2txt()
|
||||
text = re.sub(r"«\s(.*?)\s»", replace_quotes, text)
|
||||
f.write(text)
|
||||
try:
|
||||
result = self.run_grammalecte(filename)
|
||||
except FileNotFoundError as e:
|
||||
if e.filename == "grammalecte-cli.py":
|
||||
install_grammalecte()
|
||||
result = self.run_grammalecte(filename)
|
||||
if result.stdout:
|
||||
warnings = json.loads(result.stdout)
|
||||
self.manage_grammar_errors(warnings, pofile)
|
||||
self.manage_spelling_errors(warnings, pofile)
|
||||
Path(filename).unlink()
|
||||
text = pofile.rst2txt()
|
||||
text = re.sub(r"«\s(.*?)\s»", replace_quotes, text)
|
||||
warnings = grammalecte_text(text)
|
||||
self.manage_warnings(warnings, pofile)
|
||||
|
||||
def check_item(self, item: PoItem):
|
||||
"""Check an item in a `*.po` file (does nothing)."""
|
||||
pass
|
||||
|
||||
def manage_grammar_errors(self, warnings, pofile: PoFile):
|
||||
"""Manage grammar errors returned by grammalecte."""
|
||||
for warning in warnings["data"]:
|
||||
for error in warning["lGrammarErrors"]:
|
||||
if self.filter_out_grammar_error(error):
|
||||
continue
|
||||
item_index = int(warning["iParagraph"]) // 2
|
||||
item = pofile.content[item_index]
|
||||
start = max(0, int(error["nStart"]) - 40)
|
||||
end = max(0, int(error["nEnd"]) + 10)
|
||||
item.add_warning(
|
||||
self.name,
|
||||
f'{error["sMessage"]} => '
|
||||
f"###{item.msgstr_rst2txt[start:end]}###",
|
||||
)
|
||||
def manage_warnings(self, warnings: GrammalecteMessage, pofile: PoFile) -> None:
|
||||
"""Manage warnings returned by grammalecte."""
|
||||
for warning in warnings:
|
||||
if self.filter_out_grammar_error(warning) or self.filter_out_spelling_error(
|
||||
warning
|
||||
):
|
||||
continue
|
||||
item_index = warning.line // 2
|
||||
item = pofile.content[item_index]
|
||||
start = max(0, warning.start - 40)
|
||||
end = warning.end + 10
|
||||
item.add_warning(
|
||||
self.name,
|
||||
f"{warning.message} => " f"###{item.msgstr_rst2txt[start:end]}###",
|
||||
)
|
||||
|
||||
def filter_out_grammar_error(self, error):
|
||||
def filter_out_grammar_error(self, warning: GrammalecteMessage) -> bool:
|
||||
"""Return True when grammalecte error should be ignored."""
|
||||
msg = error["sRuleId"]
|
||||
if msg in (
|
||||
if not isinstance(warning, GrammalecteGrammarMessage):
|
||||
return False
|
||||
if warning.rule in (
|
||||
"esp_milieu_ligne", # double space
|
||||
"nbsp_avant_deux_points", # NBSP
|
||||
"nbsp_avant_double_ponctuation", # NBSP
|
||||
):
|
||||
return True
|
||||
if "typo_guillemets_typographiques_simples" in msg:
|
||||
if "typo_guillemets_typographiques_simples" in warning.rule:
|
||||
return True # ignore ' quotes
|
||||
msg_text = error["sMessage"]
|
||||
if msg_text in (
|
||||
if warning.message in (
|
||||
"Accord de genre erroné : « ABC » est masculin.",
|
||||
"Accord de genre erroné : « PEP » est masculin.",
|
||||
"Accord de nombre erroné : « PEP » devrait être au pluriel.",
|
||||
"Accord de genre erroné : « une entrée » est féminin, « utilisateur » est masculin.",
|
||||
):
|
||||
return True
|
||||
if "S’il s’agit d’un impératif" in msg_text:
|
||||
if error["nStart"] == 0:
|
||||
if "S’il s’agit d’un impératif" in warning.message:
|
||||
if warning.start == 0:
|
||||
# ignore imperative conjugation at begining of 1st sentence
|
||||
return True
|
||||
return False
|
||||
|
||||
def manage_spelling_errors(self, warnings, pofile: PoFile):
|
||||
"""Manage spelling errors returned by grammalecte."""
|
||||
for warning in warnings["data"]:
|
||||
for error in warning["lSpellingErrors"]:
|
||||
if self.filter_out_spelling_error(error):
|
||||
continue
|
||||
item_index = int(warning["iParagraph"]) // 2
|
||||
item = pofile.content[item_index]
|
||||
start = max(0, int(error["nStart"]) - 40)
|
||||
end = max(0, int(error["nEnd"]) + 10)
|
||||
word = error["sValue"]
|
||||
item.add_warning(
|
||||
self.name,
|
||||
f'Unknown word "{word}" in '
|
||||
f"###{item.msgstr_rst2txt[start:end]}###",
|
||||
)
|
||||
|
||||
def filter_out_spelling_error(self, error):
|
||||
def filter_out_spelling_error(self, warning: GrammalecteMessage) -> bool:
|
||||
"""Return True when grammalecte error should be ignored."""
|
||||
word = error["sValue"]
|
||||
if set(word) == {"x"}:
|
||||
if not isinstance(warning, GrammalecteSpellingMessage):
|
||||
return False
|
||||
if set(warning.word) == {"x"}:
|
||||
return True # word is xxxxx or xxxxxxxx…
|
||||
if word.strip() in self.personal_dict:
|
||||
if warning.word.strip() in self.personal_dict:
|
||||
return True # white list
|
||||
if warning.word.endswith("_"):
|
||||
return True
|
||||
if warning.word.lower() in glossary:
|
||||
return True
|
||||
if warning.word.lower() == "uplet": # partially italic word in glossary
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_personal_dict(self):
|
||||
"""
|
||||
Add spelling white list.
|
||||
|
||||
Based on
|
||||
https://raw.githubusercontent.com/python/python-docs-fr/3.8/dict
|
||||
"""
|
||||
download_request = requests.get(
|
||||
"https://raw.githubusercontent.com/python/python-docs-fr/3.8/dict"
|
||||
)
|
||||
download_request.raise_for_status()
|
||||
for line in download_request.text.splitlines():
|
||||
def _get_personal_dict(self, dict_path: str) -> None:
|
||||
if "://" in dict_path:
|
||||
download_request = requests.get(dict_path)
|
||||
download_request.raise_for_status()
|
||||
lines = download_request.text
|
||||
else:
|
||||
lines = Path(dict_path).read_text(encoding="UTF-8")
|
||||
for line in lines.splitlines():
|
||||
word = line.strip()
|
||||
self.personal_dict.add(word)
|
||||
self.personal_dict.add(word.title())
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--dict",
|
||||
nargs="*",
|
||||
dest="dicts",
|
||||
help="Personal dict files or URLs. Should contain onw word per line.",
|
||||
)
|
||||
|
||||
def install_grammalecte():
|
||||
"""Install grammalecte CLI."""
|
||||
log.warning("Missing grammalecte, trying to install it")
|
||||
tmpdirname = tempfile.mkdtemp(prefix="padpo_grammalecte_")
|
||||
tmpdirname = Path(tmpdirname)
|
||||
tmpdirname.mkdir(exist_ok=True)
|
||||
download_request = requests.get(
|
||||
"https://grammalecte.net/grammalecte/zip/Grammalecte-fr-v1.5.0.zip"
|
||||
)
|
||||
download_request.raise_for_status()
|
||||
zip_file = tmpdirname / "Grammalecte-fr-v1.5.0.zip"
|
||||
zip_file.write_bytes(download_request.content)
|
||||
with ZipFile(zip_file, "r") as zip_obj:
|
||||
zip_obj.extractall(tmpdirname / "Grammalecte-fr-v1.5.0")
|
||||
subprocess.run(
|
||||
["pip", "install", str(tmpdirname / "Grammalecte-fr-v1.5.0")]
|
||||
)
|
||||
def configure(self, args):
|
||||
"""Store the result of parse_args, to get back arguments from self.add_arguments."""
|
||||
if args.dicts:
|
||||
for dict_path in args.dicts:
|
||||
self._get_personal_dict(dict_path)
|
||||
|
|
|
@ -27,7 +27,8 @@ class NonBreakableSpaceChecker(Checker):
|
|||
prefix = item.msgstr_rst2txt[match.start(1) : match.end(1)]
|
||||
suffix = item.msgstr_rst2txt[match.start(3) : match.end(3)]
|
||||
match = item.msgstr_rst2txt[match.start(2) : match.end(2)]
|
||||
self.__add_message_space_before(item, prefix, match, suffix)
|
||||
if prefix[-1] not in ":?!.":
|
||||
self.__add_message_space_before(item, prefix, match, suffix)
|
||||
|
||||
def __add_message(self, item, prefix, match, suffix):
|
||||
item.add_error(
|
||||
|
|
|
@ -4,6 +4,9 @@ import tempfile
|
|||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import simplelogging
|
||||
|
||||
log = simplelogging.get_logger()
|
||||
|
||||
|
||||
class PullRequestInfo:
|
||||
|
@ -40,9 +43,7 @@ class PullRequestInfo:
|
|||
def pull_request_files(pull_request: str):
|
||||
"""Return pull request information."""
|
||||
pull_request = pull_request.replace("/pull/", "/pulls/")
|
||||
request = requests.get(
|
||||
f"https://api.github.com/repos/{pull_request}/files"
|
||||
)
|
||||
request = requests.get(f"https://api.github.com/repos/{pull_request}/files")
|
||||
request.raise_for_status()
|
||||
# TODO remove directory at end of execution
|
||||
temp_dir = tempfile.mkdtemp(prefix="padpo_")
|
||||
|
@ -56,5 +57,7 @@ def pull_request_files(pull_request: str):
|
|||
temp_file_dir = temp_file.parent
|
||||
temp_file_dir.mkdir(parents=True, exist_ok=True)
|
||||
temp_file.write_bytes(content_request.content)
|
||||
pr.add_file(filename, temp_file, fileinfo["patch"])
|
||||
if "patch" in fileinfo:
|
||||
# if a patch is provided (patch is small enough)
|
||||
pr.add_file(filename, temp_file, fileinfo["patch"])
|
||||
return pr
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Entry point of padpo."""
|
||||
|
||||
import argparse
|
||||
import pkg_resources
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
@ -45,10 +46,20 @@ def check_path(path, pull_request_info=None):
|
|||
return check_file(path, pull_request_info)
|
||||
|
||||
|
||||
def check_paths(paths, pull_request_info=None):
|
||||
"""Check a list of paths (`*.po` file or directory)."""
|
||||
result_errors = []
|
||||
result_warnings = []
|
||||
for path in paths:
|
||||
errors, warnings = check_path(path, pull_request_info)
|
||||
result_errors.extend(errors)
|
||||
result_warnings.extend(warnings)
|
||||
return result_errors, result_warnings
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point."""
|
||||
global log
|
||||
log = simplelogging.get_logger("__main__")
|
||||
|
||||
parser = argparse.ArgumentParser(description="Linter for *.po files.")
|
||||
parser.add_argument("-v", "--verbose", action="count", default=0)
|
||||
|
@ -59,7 +70,10 @@ def main():
|
|||
metavar="PATH",
|
||||
type=str,
|
||||
help="path of the file or directory to check",
|
||||
default="",
|
||||
default=[],
|
||||
# allow the user to provide no path at all,
|
||||
# this helps writing scripts
|
||||
nargs="*",
|
||||
)
|
||||
files.add_argument(
|
||||
"-g",
|
||||
|
@ -77,7 +91,28 @@ def main():
|
|||
help="ID of pull request in python-docs-fr repository",
|
||||
default=0,
|
||||
)
|
||||
files.add_argument("--version", action="store_true", help="Return version")
|
||||
parser.add_argument("-c", "--color", action="store_true", help="color output")
|
||||
|
||||
for checker in checkers:
|
||||
checker.add_arguments(parser)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print(pkg_resources.get_distribution("padpo").version)
|
||||
sys.exit(0)
|
||||
|
||||
if args.color:
|
||||
console_format = (
|
||||
"%(log_color)s[%(levelname)-8s]%(reset)s "
|
||||
"%(green)s%(pofile)s:%(poline)s: "
|
||||
"%(cyan)s[%(checker)s] %(message)s%(reset)s"
|
||||
)
|
||||
else:
|
||||
console_format = "%(pofile)s:%(poline)s: %(leveldesc)s: %(message)s"
|
||||
log = simplelogging.get_logger("__main__", console_format=console_format)
|
||||
|
||||
if args.verbose < 1:
|
||||
log.reduced_logging()
|
||||
elif args.verbose < 2:
|
||||
|
@ -85,18 +120,21 @@ def main():
|
|||
else:
|
||||
log.full_logging()
|
||||
|
||||
if args.input_path:
|
||||
path = args.input_path
|
||||
pull_request_info = None
|
||||
else:
|
||||
if args.github or args.python_docs_fr:
|
||||
pull_request = ""
|
||||
if args.github:
|
||||
pull_request = args.github
|
||||
if args.python_docs_fr:
|
||||
pull_request = f"python/python-docs-fr/pull/{args.python_docs_fr}"
|
||||
pull_request_info = pull_request_files(pull_request)
|
||||
path = pull_request_info.download_directory
|
||||
path = [pull_request_info.download_directory]
|
||||
else:
|
||||
path = args.input_path
|
||||
pull_request_info = None
|
||||
|
||||
errors, warnings = check_path(path, pull_request_info=pull_request_info)
|
||||
for checker in checkers:
|
||||
checker.configure(args)
|
||||
|
||||
errors, warnings = check_paths(path, pull_request_info=pull_request_info)
|
||||
if errors:
|
||||
sys.exit(1)
|
||||
|
|
|
@ -82,15 +82,11 @@ class PoItem:
|
|||
text = re.sub(r"::", r":", text)
|
||||
text = re.sub(r"``(.*?)``", r"« \1 »", text)
|
||||
text = re.sub(r"\"(.*?)\"", r"« \1 »", text)
|
||||
text = re.sub(r":pep:`(.*?)`", r"PEP \1", text)
|
||||
text = re.sub(r":[a-z:]+:`(.+?)`", r"« \1 »", text)
|
||||
text = re.sub(r":[Pp][Ee][Pp]:`(.*?)`", r"PEP \1", text)
|
||||
text = re.sub(r":[a-zA-Z:]+:`(.+?)`", r"« \1 »", text)
|
||||
text = re.sub(r"\*\*(.*?)\*\*", r"« \1 »", text)
|
||||
text = re.sub(
|
||||
r"\*(.*?)\*", r"« \1 »", text
|
||||
) # TODO sauf si déjà entre «»
|
||||
text = re.sub(
|
||||
r"`(.*?)\s*<((?:http|https|ftp)://.*?)>`_", r"\1 (« \2 »)", text
|
||||
)
|
||||
text = re.sub(r"\*(.*?)\*", r"« \1 »", text) # TODO sauf si déjà entre «»
|
||||
text = re.sub(r"`(.*?)\s*<((?:http|https|ftp)://.*?)>`_", r"\1 (« \2 »)", text)
|
||||
text = re.sub(r"<((?:http|https|ftp)://.*?)>", r"« \1 »", text)
|
||||
return text
|
||||
|
||||
|
@ -146,14 +142,28 @@ class PoFile:
|
|||
for item in self.content:
|
||||
if not item.inside_pull_request:
|
||||
continue
|
||||
prefix = f"{self.path}:{item.lineno_start:-4} %s"
|
||||
log.debug(prefix, "")
|
||||
for message in item.warnings:
|
||||
if isinstance(message, Error):
|
||||
log.error(prefix, message)
|
||||
log.error(
|
||||
message.text,
|
||||
extra={
|
||||
"pofile": self.path,
|
||||
"poline": item.lineno_start,
|
||||
"checker": message.checker_name,
|
||||
"leveldesc": "error",
|
||||
},
|
||||
)
|
||||
errors.append(message)
|
||||
elif isinstance(message, Warning):
|
||||
log.warning(prefix, message)
|
||||
log.warning(
|
||||
message.text,
|
||||
extra={
|
||||
"pofile": self.path,
|
||||
"poline": item.lineno_start,
|
||||
"checker": message.checker_name,
|
||||
"leveldesc": "warning",
|
||||
},
|
||||
)
|
||||
warnings.append(message)
|
||||
return errors, warnings
|
||||
|
||||
|
|
1712
poetry.lock
generated
1712
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,52 +1,60 @@
|
|||
[tool.poetry]
|
||||
name = "padpo"
|
||||
version = "0.5.0"
|
||||
version = "0.11.0"
|
||||
description = "Linter for gettext files"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Operating System :: OS Independent"
|
||||
# TODO: Add more
|
||||
]
|
||||
authors = ["Vincent Poulailleau <vpoulailleau@gmail.com>"]
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/vpoulailleau/padpo"
|
||||
homepage = "https://github.com/vpoulailleau/padpo"
|
||||
documentation = "https://github.com/vpoulailleau/padpo"
|
||||
keywords = ["gettext", "linter"]
|
||||
repository = "https://github.com/AFPy/padpo"
|
||||
homepage = "https://github.com/AFPy/padpo"
|
||||
documentation = "https://github.com/AFPy/padpo"
|
||||
keywords = ["gettext", "linter", "grammalecte"]
|
||||
license = "BSD-3-Clause"
|
||||
include = ["padpo/**/*.py"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
requests = "2.22.0"
|
||||
simplelogging = "0.10.0"
|
||||
wheel = "0.33.6" # for grammalecte
|
||||
setuptools = "42.0.2" # for grammalecte
|
||||
pygrammalecte = "^1.3.0"
|
||||
requests = "^2.20.0"
|
||||
simplelogging = ">=0.10,<0.12"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "5.3.1"
|
||||
pytest-cov = "2.8.1"
|
||||
tox = "3.14.1"
|
||||
twine = "2.0.0"
|
||||
python-dev-tools = {version = ">=2020.9.4", python = ">=3.7,<4.0"}
|
||||
|
||||
[tool.poetry.scripts]
|
||||
padpo = "padpo.padpo:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
|
||||
[tool.tox]
|
||||
legacy_tox_ini = """
|
||||
[tox]
|
||||
isolated_build = True
|
||||
envlist = py37, py38
|
||||
envlist = py37, py38, py39
|
||||
|
||||
[testenv]
|
||||
whitelist_externals = poetry
|
||||
whitelist_externals =
|
||||
poetry
|
||||
echo
|
||||
sed
|
||||
cp
|
||||
changedir = {toxinidir}/tests
|
||||
commands =
|
||||
poetry install -v
|
||||
# poetry run pytest -s -vv --cov={envsitepackagesdir}/padpo
|
||||
poetry run pytest -s -vv --cov=padpo
|
||||
"""
|
||||
poetry run coverage xml
|
||||
echo 'fix travis bug'
|
||||
sed --in-place -e 's#//home#/home#g' coverage.xml
|
||||
echo 'fix codeclimate bug, use relative path'
|
||||
sed --in-place -e 's#/home.*vpoulailleau/padpo/##g' coverage.xml
|
||||
cp coverage.xml ../coverage.xml
|
||||
"""
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
|
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 663 KiB After Width: | Height: | Size: 645 KiB |
5
tests/po_with_warnings/tuple.po
Normal file
5
tests/po_with_warnings/tuple.po
Normal file
|
@ -0,0 +1,5 @@
|
|||
#: ../Doc/library/typing.rst:19
|
||||
msgid ""
|
||||
"tuple"
|
||||
msgstr ""
|
||||
"n-uplet"
|
5
tests/po_without_warnings/internal_links.po
Normal file
5
tests/po_without_warnings/internal_links.po
Normal file
|
@ -0,0 +1,5 @@
|
|||
#: ../Doc/library/typing.rst:19
|
||||
msgid ""
|
||||
"internal_link_"
|
||||
msgstr ""
|
||||
"cemotnexistepas_"
|
9
tests/po_without_warnings/nbsp.po
Normal file
9
tests/po_without_warnings/nbsp.po
Normal file
|
@ -0,0 +1,9 @@
|
|||
#: ../Doc/library/typing.rst:19
|
||||
msgid ""
|
||||
"Non breakable space.::"
|
||||
msgstr ""
|
||||
"Espace insécable. ::"
|
||||
" Espace insécable ! ::"
|
||||
" Espace insécable ? ::"
|
||||
" Oui !"
|
||||
" Non ?"
|
5
tests/po_without_warnings/tuple.po
Normal file
5
tests/po_without_warnings/tuple.po
Normal file
|
@ -0,0 +1,5 @@
|
|||
#: ../Doc/library/typing.rst:19
|
||||
msgid ""
|
||||
"tuple"
|
||||
msgstr ""
|
||||
"*n*-uplet"
|
35
tests/test_po_warnings.py
Normal file
35
tests/test_po_warnings.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""Test known to be good files."""
|
||||
from pathlib import Path
|
||||
|
||||
from padpo.padpo import check_file
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
"""Parametrize tests according to po_without_warnings directory content."""
|
||||
if "known_good_po_file" in metafunc.fixturenames:
|
||||
# assume using tox (that cd into tests directory)
|
||||
files = list(Path("./po_without_warnings").rglob("*.po"))
|
||||
files.extend(Path("./tests/po_without_warnings").rglob("*.po"))
|
||||
metafunc.parametrize(
|
||||
"known_good_po_file", [str(file_path) for file_path in files]
|
||||
)
|
||||
if "known_bad_po_file" in metafunc.fixturenames:
|
||||
# assume using tox (that cd into tests directory)
|
||||
files = list(Path("./po_with_warnings").rglob("*.po"))
|
||||
files.extend(Path("./tests/po_with_warnings").rglob("*.po"))
|
||||
metafunc.parametrize(
|
||||
"known_bad_po_file", [str(file_path) for file_path in files]
|
||||
)
|
||||
|
||||
|
||||
def test_no_error_no_warning(known_good_po_file):
|
||||
"""Test known to be good files."""
|
||||
errors, warnings = check_file(known_good_po_file)
|
||||
assert not errors
|
||||
assert not warnings
|
||||
|
||||
|
||||
def test_error_or_warning(known_bad_po_file):
|
||||
"""Test known to be bad files."""
|
||||
errors, warnings = check_file(known_bad_po_file)
|
||||
assert errors or warnings
|
|
@ -1,22 +0,0 @@
|
|||
"""Test known to be good files."""
|
||||
from pathlib import Path
|
||||
|
||||
from padpo.padpo import check_file
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
"""Parametrize tests according to po_without_warnings directory content."""
|
||||
if "known_good_po_file" in metafunc.fixturenames:
|
||||
# assume using tox (that cd into tests directory)
|
||||
directory = Path("./po_without_warnings")
|
||||
files = directory.rglob("*.po")
|
||||
metafunc.parametrize(
|
||||
"known_good_po_file", [str(file) for file in files]
|
||||
)
|
||||
|
||||
|
||||
def test_no_error_no_warning(known_good_po_file):
|
||||
"""Test known to be good files."""
|
||||
errors, warnings = check_file(known_good_po_file)
|
||||
assert not errors
|
||||
assert not warnings
|
Loading…
Reference in New Issue
Block a user