# sphinx-lint Un linter pour ta doc.
Julien Palard ## Julien Palard - CPython core dev - Sysadmin à l’AFPy - hackinscience.org Notes: - Je m’occupe 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 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 n’aurait **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 `_ `Un lien anonyme `__ ``` 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 s’appelle *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à, c’est 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)(?]""" UNICODE_AFTER = r"[\p{Pe}\p{Pi}\p{Pf}\p{Pd}\p{Po}]" re.compile( fr"(?{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""" (? {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 ! # C’est utilisé ? - CPython (forcément…), devguide, peps, traductions, ... - neo4j-python-driver - pandas - Spyder IDE - Sympy - Sphinx !!! # Ça trouve des bugs ? ## Dans les PEPS ```diff -`vcs ` library as +`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, c’est un bon test (beaucoup de rst dans du rst). - Aucun dans la doc de Python, c’est 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 s’utilise ? ```text $ python -m pip install sphinx-lint $ sphinx-lint ``` # Bouh c’est pas packagé pour [distrib] Bah, fais-le. Notes: Pour Debian je veux bien le faire ;) # Questions ? - Twitter : [@sizeof](https://twitter.com/sizeof) - 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 !?