talks/2023-pyconfr-sphinx-lint.md

481 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# sphinx-lint
<!-- .slide: data-background="static/2023-snakes.svg" -->
Un linter pour ta doc.
<br/>
Julien Palard
## Julien Palard
<!-- .slide: data-background="static/background.jpg" -->
- CPython core dev
- Sysadmin à lAFPy
- hackinscience.org
Notes:
- Je moccupe surtout de docs.python.org
- Formateur, indépendant, faites passer le mot :)
# Pourquoi ?
Notes:
Trouver des fautes de frappe dans du rst, ni plus, ni moins.
## Depuis quand ?
```text
Author: Georg Brandl <georg@python.org>
Date: Sat Jan 3 20:30:15 2009 +0000
```
Dans `cpython` dans `Doc/tools/rstlint.py` !
Notes:
Oui oui, Georg, l'auteur de Sphinx-doc.
# La motivation
`make suspicious`
Bien connu pour ses faux positifs, n'est-ce pas ?
Notes:
Lui il date du portage de la doc de LaTeX à Sphinx, il était là pour
repérer les erreurs de portage. Il date donc de 2009 aussi.
## La motivation
Remplacer `make suspicious` par un outil qui naurait **aucun** faux positif.
Notes:
- Aucun faux positif == cool pour les contributeurs !
- Et qui serait rapide !
- Parler de la lenteur de `make suspicious`.
## La motivation
Sortir `rstlint.py` de `cpython/Doc/tools` pour que tout le monde puisse l'utiliser.
Notes: Et pour faciliter la contribution.
## La motivation
![](static/2023-pyconfr-issue-open.png)
# Parsons du rst !
## Le reStructuredText
Le balisage :
```text
*En italique*
**En gras**
`Interprété` (dont le rôle peut changer)
``litéral``
Lien_
`Autre lien`_
`Encore un lien <https://...>`_
`Un lien anonyme <https://...>`__
```
Notes: Comme en Markdown, comme en Org.
## Le reStructuredText
Les textes interprétés :
```text
`while` C'est du texte interprété.
:keyword:`while` Du texte interprété avec un « role marker »
`while`:keyword: Ça lui donne un sens particulier.
```
## Le reStructuredText
Donc oui le balisage et le texte interprété peuvent jouer le même rôle :
```text
*ça* c'est pareil que :emphasis:`ça`.
**ça** c'est pareil que :strong:`ça`.
``ça`` c'est pareil que :literal:`ça`.
```
Notes:
Mais comme en Python on a plus de méthodes que d'opérateurs…
En rst on a plus de rôles que de balises !
## Le reStructuredText
Les directives :
```text
.. image:: Lenna.jpg
.. important::
Elle sappelle *Lena Sjööblom*.
```
Notes:
Je prends un gros raccourci : les directives ne sont qu'un bout de
tout le balisage "explicite" comme les commentaires, les notes de bas
de page, ...
Retenez-bien que le corps d'une directive peut contenir du reStructuredText !
## Parsons du rst !
Jusque-là, cest facile !
## Parsons du rst !
Maintenant, parsez ça, avec vos yeux :
```text
*2 * x *a **b *.txt*
```
Notes:
C'est tout en italique !
## Parsons du rst !
Vous voyez du balisage, vous, ici ?
```text
2 * x ** 2 + 3 * x ** 3
a || b
"*" '|' (*) [*] {*} <*> *
2*x a**b O(N**2) e**(x*y)
```
Notes:
Pas le parseur. Mais bon les règles sont bien documentées ♥
Mais on voit que les humains peuvent facilement faire des erreurs ici.
## Parsons du rst !
Regardons du côté des directives :
```text
.. code-block:: python
def fib(n):
if n in (0, 1):
return 1
x = n // 2
return fib(x-1) * fib(n-x-1) + fib(x) * fib(n-x)
```
Notes:
Une directive peut avoir un corps, ici du code.
## Parsons du rst !
Regardons du côté des directives :
```text
.. image:: Lenna.jpg
:height: 330px
:width: 330px
:scale: 50 %
:alt: Lenna Sjööblom
```
Notes:
Une directive peut avoir des options mais **pas de corps**.
## Parsons du rst !
Regardons du côté des directives :
```text
.. csv-table:: Domaines d'organismes publics
:header: "name", "http_status", "https_status"
:widths: 20, 10, 10
cgf.pf, 200, 200
dun.fr, 200, 200
caf.pf, 200, 200
aze.fr, 200, 200
```
Notes:
Une directive peut avoir des options **et** un corps.
## Quiz !
```text
.. code-block:: python
items = ["titi", "tata", "toto"]
print(*items)
.. important::
items = ["titi", "tata", "toto"]
print(*items)
```
## Quiz !
```text
.. csv-table:: Domaines d'organismes publics
:header: "name", "http_status", "https_status"
:widths: 20, 10, 10
cgf.pf, 200, 200, OK
dun.fr, 200, 200, OK
.. csv-table:: Domaines d'organismes publics
:header: "name", "http_status", "https_status"
:widths: 20, 10, 10
caf.pf
aze.fr
```
Notes:
Les valeurs manquantes sont moins graves que les valeurs surnuméraires.
## Ne parsons pas de rst...
De toutes façons `sphinx-lint` ne cherche pas du rst valide, il cherche du rst invalide !
## Mes amies les regex
```python
SIMPLENAME = r"(?:(?!_)\w)+(?:[-._+:](?:(?!_)\w)+)*"
ROLE_GLUED_WITH_WORD_RE = re.compile(
fr"(^|\s)(?<!:){SIMPLENAME}:`(?!`)")
ROLE_WITH_NO_BACKTICKS_RE = re.compile(
fr"(^|\s):{SIMPLENAME}:(?![`:])[^\s`]+(\s|$)")
ROLE_MISSING_RIGHT_COLON_RE = re.compile(
fr"(^|\s):{SIMPLENAME}`(?!`)")
ROLE_MISSING_CLOSING_BACKTICK_RE = re.compile(
fr"({ROLE_HEAD}`[^`]+?)[^`]*$")
```
Notes: Parfois lisibles.
## Mes amies les regex
```python
ASCII_BEFORE = r"""[-:/'"<(\[{]"""
UNICODE_BEFORE = r"[\p{Ps}\p{Pi}\p{Pf}\p{Pd}\p{Po}]"
ASCII_AFTER = r"""[-.,:;!?/'")\]}>]"""
UNICODE_AFTER = r"[\p{Pe}\p{Pi}\p{Pf}\p{Pd}\p{Po}]"
re.compile(
fr"(?<!\x00)(?<=^|\s|{ASCII_BEFORE}|{UNICODE_BEFORE})"
fr"(?P<inline_markup>{start_string}\S{QUOTE_PAIRS_NLB}"
fr".*?(?<=\S){end_string})"
fr"(?=$|\s|\x00|{ASCII_AFTER}|{UNICODE_AFTER})"
)
```
Notes: Parfois pas. Mais vous connaissez `re.VERBOSE` ?
## Mes amies les regex
```python
re.compile(
fr"""
(?<!\x00) # Both inline markup start-string and end-string
# must not be preceded by an unescaped backslash
(?<= # Inline markup start-strings must:
^| # start a text block
\s| # or be immediately preceded by whitespace,
{ASCII_BEFORE}| # one of the ASCII characters or a
{UNICODE_BEFORE} # similar non-ASCII punctuation char.
)
(?P<inline_markup>
{start_string} # Inline markup start
\S # Inline markup start-strings must be
# immediately followed by non-whitespace.
# The inline markup end-string must be
# separated by at least one character
# from the start-string.
{QUOTE_PAIRS_NEGATIVE_LOOKBEHIND}
.*?
(?<=\S) # Inline markup end-strings must be
# immediately preceded by non-whitespace.
{end_string} # Inline markup end
)
(?= # Inline markup end-strings must
$| # end a text block or
\s| # be immediately followed by whitespace,
\x00|
{ASCII_AFTER}| # one of the ASCII characters or a
{UNICODE_AFTER} # similar non-ASCII punctuation char.
)
""",
flags=re.VERBOSE | re.DOTALL,
)
```
Notes: Les commentaires sont un copié-collé de la spec !
# Cest utilisé ?
- CPython (forcément…), devguide, peps, traductions, ...
- neo4j-python-driver
- pandas
- Spyder IDE
- Sympy
- Sphinx !!!
# Ça trouve des bugs ?
## Dans les PEPS
```diff
-`vcs <https://pythonhosted.org/vcs/>` library as
+`vcs <https://pythonhosted.org/vcs/>`_ library as
```
## Dans sympy
```diff
-... autoclass:: sympy.assumptions.predicates.order
+.. autoclass:: sympy.assumptions.predicates.order
```
```diff
-:data:``sympy.parsing.sympy_parser.transformations``
+:data:`sympy.parsing.sympy_parser.transformations`
```
```diff
- `KanesMethod`` objects.
+ ``KanesMethod`` objects.
```
## Dans la traduction
> I just run the tool against library/ as a whole and it spit out a loooot of messages, so I thought that maybe it didn't work with our po files.
## Dans la traduction
> After closer inspecting two of these files I found that they were indeed proper errors
```diff
-"datetime`. :const:`MINYEAR` es` `1``."
+"datetime`. :const:`MINYEAR` es ``1``."
```
Notes:
Ils ont réparé plus de 300 erreurs !
# Et les perfs ?
```text
$ time make suspicious
real 1m10.416s
user 3m28.918s
sys 0m3.127s
```
```text
$ time make check # (c'est sphinx-lint derrière)
real 0m6.351s
user 0m43.478s
sys 0m0.227s
```
# Et les faux positifs ?
- `make suspicious` → 400
- `make checks` → 0
## Et les faux positifs ?
Ils se cachent bien :
- Aucun dans la doc de Sphinx, cest un bon test (beaucoup de rst dans du rst).
- Aucun dans la doc de Python, cest un bon test (286k lignes de rst).
## Et `make suspicious` ?
![](static/2023-pyconfr-issue-open.png)
![](static/2023-pyconfr-issue-close.png)
# OK, vendu !
Mais comment ça sutilise ?
```text
$ python -m pip install sphinx-lint
$ sphinx-lint
```
# Bouh cest pas packagé pour [distrib]
Bah, fais-le.
Notes: Pour Debian je veux bien le faire ;)
# Questions ?
- <s>Twitter : [@sizeof](https://twitter.com/sizeof)</s>
- Mastodon : [@mdk@mamot.fr](https://mamot.fr/@mdk)
- XMPP : mdk@chapril.org
- HTTP : https://mdk.fr (et https://discuss.afpy.org)
- SMTP : julien@python.org
- IRC : mdk sur irc.libera.chat (#python-fr, #afpy)
- Whatsapp : HaHa. Jamais.
- Instagram : Et puis quoi encore ?
- TikTok : Euh, bon, stop maintenant !?